phoenix-todo-list
phoenix-todo-list copied to clipboard
Yet another todo list. Made using the Phoenix web framework. Features: Basic CRUD, LiveView CRUD, REST API, OpenAPI spec, Playwright E2E tests, GitHub Actions CI, and more!
phoenix-todo-list
All commands in this document should be performed from the project root directory.
Yet another todo list.
Made using the Phoenix web framework, and enhanced with Phoenix LiveView.
Features:
- Basic CRUD
- LiveView CRUD
- REST API
- OpenAPI spec available in JSON and YAML
- Swagger Web UI client is also available
- Insomnia spec for easy HTTP/API endpoint debugging
- Elixir tests (ExUnit)
- Javascript-based unit tests (Vitest)
- Javascript-based E2E tests (Playwright)
- GitHub Actions CI
- Releases (vanilla/Docker/fly.io)
- Supports
x86_64+aarch64(ARM64v8) Docker images
- Supports
- Supports a variety of container-based environments using Docker/Podman
- EditorConfig (standardizes file formatting: spaces per line, etc.)
- Enforces standardized commit messages with
git-conventional-commits - Uses
justtask runner- Run
justby itself to see the list of available commands.
- Run
- Uses PromEx to generate metrics data for use with Prometheus + Grafana dashboards
- Uses Sentry for error monitoring
- Uses robots.txt and sitemap.xml to assist with SEO and search engine discoverability
Getting Started
First, you'll need to clone this repo: git clone https://github.com/arcanemachine/phoenix-todo-list
Working in a dev Environment
NOTE: In order to do things via just, you will need to install the just task runner.
- Setup your local environment variables:
- Via
just:just dotenv-generate - Manually:
- Run the
./support/scripts/dotenv-generatescript to generate a.envfile to get you started.- You can set custom/private environment variables in
.envso that they will not be accidentally committed to source control.
- You can set custom/private environment variables in
- Run the
- Load the environment variables into the current terminal session:
. .env- NOTE:
direnvis a great tool to automatically source environment files on a per-project basis.
- NOTE:
- Via
- Install the
npmdependencies:- Via
just:just js-dependencies-install - Manually:
- Ensure that
npmis installed and working on your computer. - Navigate to the directory
assets/and runnpm install.
- Ensure that
- Via
- Ensure that a Postgres server is running:
- Via
just:just postgres(must have Docker installed). - Manually:
- Ensure the Postgres server is installed and running in your desired location, and ensure that your Phoenix application can access the database.
- Via
- Setup the server:
- Via
just:just setup - Manually:
- Run
mix deps.getto fetch the dependencies. - Setup the database:
mix ecto.setup
- Run
- Via
- Run the server:
- Via
just:just start - The manual way:
mix phx.server
- Via
- Your server should now be accessible on
localhost:4001.- It may take a moment for
esbuildto build its initial bundle.- The layout of the page will look ugly while this is happening.
- This project's ports are set by configuring the
PORTenvironment variable (e.g. in the.envfile).- Production: The default port is
PORT(e.g. 4000) - Development: The default port is
PORT + 1(e.g. 4001) - Testing: The default port is
PORT + 2(e.g. 4002)
- Production: The default port is
- It may take a moment for
Testing
Before running any tests, make sure that you have followed the instructions in the above section "Getting Started".
Notes:
- To run all tests at once, run
just test.- The first test run may appear to hang when the console says
Resetting the database....- Elixir may need a minute or two to compile dependencies for the
testMIX_ENV.
- Elixir may need a minute or two to compile dependencies for the
- The first test run may appear to hang when the console says
- A Postgres server must be running for the tests to pass.
- You may need to create a test database:
MIX_ENV=test mix ecto.create - If any errors occur during the tests, try resetting the test database:
MIX_ENV=test mix ecto.reset- For example, the E2E test scripts reset the database between test runs. However, if the script is aborted, the database may not be reset, which can effect the results of
mix test.
- For example, the E2E test scripts reset the database between test runs. However, if the script is aborted, the database may not be reset, which can effect the results of
- You may need to create a test database:
Elixir-Based Tests
Run the Elixir-based tests using any of these commands:
just test-elixir- Use thejusttask runner to run the testsmix test- The regular method of running the tests../support/scripts/test-elixir- A convenience script for running the Elixir tests.- This script clears the test database before running the tests. This prevents any issues that may be caused when the test database is not cleared, e.g. during failed E2E test run.
./support/scripts/test-elixir-watch- A convenience script for running the Elixir-based tests in watch mode.
Note:
- The first test run may appear to hang when the console says
Resetting the database....- Elixir may need a minute or two to compile dependencies for the
testMIX_ENV.
- Elixir may need a minute or two to compile dependencies for the
Javascript-Based Tests
Before running any Javascript-based tests:
- Ensure that
npmis installed and working on your system. - Navigate to the
test/js/directory and runnpm install
Then, navigate back to the project root directory and continue reading for instructions on how to run this project's Javascript-based tests.
JS Unit Tests (Vitest)
Run the Javascript-based unit tests using any of these commands:
just test-js- Use thejusttask runner to run the tests./support/scripts/test-js- A convenience script for running the Vitest unit tests../support/scripts/test-js-watch- A convenience script for running the Vitest unit tests in watch mode.cd assets && npx playwright test- Navigate to the JS test root directory and run the JS E2E tests directly vianpx.
End-To-End Tests (Playwright)
Run the Javascript-based end-to-end (E2E) Playwright tests using any of these commands:
just test-e2e- Use thejusttask runner to run the tests./support/scripts/test-e2e- A convenience script for running the Playwright E2E tests../support/scripts/test-e2e-watch- A convenience script for running the Playwright E2E tests in watch mode.cd assets && npm run test-e2e- Navigate to the JS test root directory and run the JS unit tests directly.
Releases
Releases can be created for either a vanilla/bare metal deployment, or for a Docker-based deployment.
Creating a Release
First Steps
Before you create a release, ensure that your environment variables are set correctly. You can use direnv to easily load your environment when navigating within this project's directories.
Navigate to the project root directory and set up your environment variables:
- You can set custom/private environment variables in
.envso that they will not be accidentally committed to source control- Use the environment generator script to generate a
.envfile in the project root directory. You can modify this.envfile as needed.- To run the script:
./support/scripts/dotenv-generate- Or,
just dotenv-generate
- To run the script:
- Use the environment generator script to generate a
Vanilla/Bare Metal Deployment
Run the following commands from the project root directory:
- Create a release using the helper script:
./support/scripts/elixir-release-create
- Make sure that Postgres is running:
- Use
pg_isready:- e.g.
pg_isreadyorpg_isready -h localhostorpg_isready -h your-postgres-ip-address-or-domain
- e.g.
- Use
- Set up the database in Postgres:
- Spawn a shell as the
postgresuser:sudo -iu postgres
- Open the Postgres terminal:
psql -U postgres
- Create a new database user:
CREATE USER your_postgres_user WITH PASSWORD 'your_postgres_password';
- Create the database and grant privileges to the new user:
CREATE DATABASE todo_list;GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
- TODO: Configure Postgres settings like in a Django project?
- (e.g.
client_encoding,default_transaction_isolation,timezone, etc.)
- (e.g.
- Exit the Postgres prompt:
\q
- Spawn a shell as the
- Set up the Phoenix server:
- Run migrations:
MIX_ENV=prod ./_build/prod/rel/todo_list/bin/migrate
- Start the server:
MIX_ENV=prod PHX_SERVER=true ./_build/prod/rel/todo_list/bin/server
- Run migrations:
Docker/Podman Deployment
When using Podman, you can use podman-compose to manage your multi-container services.
However, podman-compose may have issues under certain circumstances (e.g. I have run into issues with it on aarch64 systems). To resolve this issue, docker-compose can be configured as a drop-in replacement for podman-compose.
Note that docker-compose must be configured (instructions below) to use the Podman socket instead of the default Docker socket.
Using docker-compose With Podman
NOTE: In order for this to work, you will need to install an older version of docker-compose. It is not unreasonable to assume this situation will stop working at some point in the future. For now, I find it to be a useful workaround when podman-compose isn't yet up to the task.
- Install
docker-composev1.29.2:sudo apt install docker-compose- Note:
docker-composev2 has been re-written in Go and is not compatible with Podman as of this writing. v1.29.2 is the last version currently supported.
- Note:
- Set the socket path when running
docker-composecommands:- With an environment variable:
DOCKER_HOST="unix:$(podman info --format '{{.Host.RemoteSocket.Path}}')" docker-compose up - Or, with the
-Hflag:docker-compose -H "unix:$(podman info --format '{{.Host.RemoteSocket.Path}}')" up
- With an environment variable:
Building a Release as a Docker Image
Run the following commands from the project root directory:
- Create a release using the helper script:
./support/scripts/elixir-release-create- Or,
just release
- Build a container image:
- Docker:
docker build -t phoenix-todo-list . - Podman:
podman build -t phoenix-todo-list .
- Docker:
To push an image to Docker Hub:
- Ensure that you have built an image using the instructions above.
- Login to your Docker Hub account:
- Examples:
- Docker:
docker login - Podman:
podman login docker.io
- Docker:
- If you have 2FA enabled, you may need to login using an Access Token instead of a password.
- Docker will notify you when attempting to login with a password, but Podman will fail silently.
- Examples:
- Push the image to Docker Hub:
- Docker:
docker push arcanemachine/phoenix-todo-list - Podman:
podman push arcanemachine/phoenix-todo-list
- Docker:
Building an aarch64 Image
To build an aarch64 (a.k.a ARM64/armv8/arm64v8) image, follow the instructions in the previous section, but do so from an aarch64 machine. This will produce an aarch64-compatible image.
aarch64 images are tagged with the aarch64 tag, e.g. docker.io/arcanemachine/phoenix-todo-list:aarch64.
When generating a dotenv file, the generator script will detect your CPU architecture (x86_64 or aarch64) so you automatically pull the proper image when using the deployment scripts in ./support/scripts/.
Running a Basic Phoenix Container
Using a Locally-Built Image
A basic compose.yaml file can be found in the project root directory. It exposes a plain Phoenix container.
To run this barebones container, run the following commands from the project root directory:
- First, ensure that you have a Postgres server running locally.
- Build the Docker image.
- Run the Compose file:
- Docker:
docker compose up - Podman:
podman-compose up
- Docker:
Using the Docker Hub Image
Run the following command from the project root directory:
- Docker:
docker compose -f support/containers/compose.phoenix.yaml up - Podman:
podman-compose -f support/containers/compose.phoenix.yaml up
Running an aarch64 Container
This project supports the creation and use of containers for the x86_64 and aarch64 CPU architectures.
- The default container image tag on Docker Hub (
latest) supports thex86_64architecture. - The
aarch64image tag for this project on Docker Hub supports theaarch64architecture.- e.g.
docker.io/arcanemachine/phoenix-todo-list:aarch64
- e.g.
- To use the
aarch64container with this project's compose files (located insupport/containers/), ensure theIMAGE_TAGenvironment variable is set toaarch64:- Examples:
- Docker:
IMAGE_TAG=aarch64 docker compose -f support/containers/compose.phoenix.yaml up - Podman:
IMAGE_TAG=aarch64 podman-compose -f support/containers/compose.phoenix.yaml up
- Docker:
- NOTE: The generated
.envfile (created by runningjust dotenv-generate) should have the proper CPU architecture for your machine in theIMAGE_TAGvariable.
- Examples:
Other Docker/Podman Deployment Procedures
For other Docker/Podman container procedures, see /support/containers/README.md.
Remote Deployment
Fly.io
Before continuing, ensure that flyctl is installed.
To deploy via fly.io, you must use the Dockerfile in the support/ directory. The Dockerfile in the project root directory is just a symlink, so you can safely delete it and symlink the Fly Dockerfile there instead:
rm ./Dockerfile && ln -s support/containers/Dockerfile.fly Dockerfile
The fly.io Dockerfile is essentially the same as the default Dockerfile generated by Phoenix, but has a few additions to make it work with fly.io. These additions are created when creating the project with flyctl.
- NOTE: The default Dockerfile generated by
flyctlmay have some issues with Tailwind and other NPM dependencies. The modified Dockerfiles used in this project have a few modifications designed to mitigate this.
This project has a fly.toml file. To create a new one, run fly launch and follow the prompt.
To deploy the project, run flyctl deploy.
Locations of Dependencies
There are several types of dependencies throughout this project that should be kept up to date:
- Elixir:
mix.exsconfig/config.exs:esbuild,tailwind
- Javascript (npm):
assets/js/
- Containers (Docker/Podman):
./support/containers/compose.*.yaml./support/containers/Dockerfile.*./support/scripts/loadtest-k6