# phoenix-chat-example: a step-by-step Phoenix chat tutorial you can actually run

> dwyl's phoenix-chat-example is a tutorial repository that walks a complete beginner through building, testing and deploying a real time chat application in Phoenix and Elixir, from the mix phx.new generator to a live deployment on Fly.io. It suits someone learning Phoenix who wants a finished app to run before reading a word, and it is a poor fit if you want a production chat product rather than a teaching example.

**dwyl/phoenix-chat-example** — 💬 The Step-by-Step Beginners Tutorial for Building, Testing & Deploying a Chat app in Phoenix 🚀

- Repository: https://github.com/dwyl/phoenix-chat-example
- Website: https://phoenix-chat.fly.dev
- Stars: 817 · Forks: 96
- Language: Elixir
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/dwyl-phoenix-chat-example

## A finished app you can run before you read a page of the tutorial

The repository is presented as a step-by-step tutorial for building, testing and deploying a chat app in Phoenix, and the structure of the README is the tutorial itself.

The first thing it asks you to do is not to write any code. It tells you to check you have everything before starting, then to first run the finished app, with three named sub-steps: clone the project, install the dependencies, and run the app. The live version is published at phoenix-chat.fly.dev.

That order is a deliberate pedagogical choice and it is the most useful thing about the repository. Most example applications show a few basics and then stop, and the tutorial names that problem directly, arguing that beginners are left lost or confused about what to do or learn next, and that very few tutorials consider testing, deployment, documentation or the other enhancements that are part of the real world of building and running apps.

The What section lists the ground the tutorial covers. You create a Phoenix app from scratch with the mix phx.new chat generator command, add a channel so the app can talk over WebSockets, implement a front end in plain JavaScript, described as ES5 without any libraries, add an Ecto schema to define the database table for storing messages, write the functions to save message and sender data, test that everything works, and deploy to Fly.io.

The project is written in Elixir, the last push to the repository was on 2026-10-09, there are no GitHub releases, and the repository does not declare a licence that GitHub can classify.

## Just-in-time learning, and the configuration it skips on purpose

The tutorial explains its own method, and the method is unusual enough to be worth quoting.

Initially the tutorial deliberately skips over configuration files and Phoenix internals, because beginners do not need to know about them to get started, and it returns to them when needed. The stated preference is just-in-time learning, when you need it, because it is immediately obvious and practical why you are learning something.

That is a coherent position and it has a real cost. A reader who follows along and then opens a real Phoenix codebase will find that the tutorial's project is thinner than a production one in exactly the places the tutorial skipped. The answer the tutorial gives is that you learn those when you need them, but the reader has to be the one who notices they are needed.

The steps themselves are numbered and they are the outline of a small real time application.

Step one is to create the app and run the tests. Step two is to create the WebSocket channel. Step three updates the template file for the user interface, with a sub-step to update the layout template and another to update the page_controller_test.exs, which is the part people usually skip and then regret. Step four updates the client code in App.js, with a sub-step to comment out lines in user_socket.js.

The naming is worth pausing on. The tutorial asks you to comment out lines in the generated socket file, which is a change to generated code. That is a reasonable teaching move when the goal is to make the smallest possible diff to a working example, and it is exactly the kind of change that a real project would not make, since regenerating the app would reintroduce the lines. The tutorial is teaching the shape of a Phoenix application rather than how to maintain one.

The author is explicit about the audience, complete beginners, with a My First Phoenix app, and says the assumption is made as little as possible, with an invitation to open an issue if a step seems skipped.

## The message history is the part that makes it a real application

Steps five through ten are where the example stops being a demo and starts being an application with state.

Step five generates the database schema to store the chat history, step six runs the Ecto migration that creates the database table, and step six and one asks you to review the messages table schema rather than move on. Step seven inserts messages into the database. Step eight loads existing messages when someone joins the chat, and step nine sends those existing messages to the client when they join. Step ten is the checkpoint, the point at which the chat app saves messages.

The architecture that produces is the standard one for Phoenix, and the tutorial spells out the division of labour. The client code in App.js sends and receives messages over the WebSocket channel, the channel is the server side of that conversation, and the Ecto schema and the database functions store the history. The Ecto topics on the repository list confirm the stack.

The two steps that deserve attention are eight and nine, because they are where the naive version of a chat example usually falls apart. A chat that only broadcasts to whoever is connected when the message is sent loses the history for anyone who was not in the room, and it loses it again for anyone who joins later. Loading the existing messages when someone joins, and then sending them to that client, is what turns a demonstration into something a user can rely on.

That said, this is where the tutorial's own scope shows. There is no pagination in the steps described, no retention policy, and no index discussion. A chat history that grows without bound in a single table is a fine tutorial artefact and a problem in production, and the tutorial does not claim to be more than the former.

## The section on what is not tested is the part to read twice

Testing gets four numbered steps, and the third of them is unusual.

Step eleven runs the default generated tests. Step twelve walks through understanding the channel tests, with a sub-step to analyse one test in detail. Step thirteen asks what is not tested, and then adds excoveralls as a development dependency in mix.exs, creates a new file called coveralls.json, runs the tests with coverage checking, and finally writes a test for the untested function.

That last sequence is the strongest argument for this repository existing. The tutorial does not simply assert that the app is tested, it shows a function that is not covered, and then shows the process of adding a coverage dependency, configuring it, running the check and closing the gap. For a beginner, that sequence is the difference between knowing that coverage exists and understanding why you would want it.

The supporting files in the repository root match that story. There is a coveralls.json at the top level, a .formatter.exs for the Elixir formatter, and a codecov badge in the README pointing at the coverage service for this project, alongside a CI workflow badge for the build.

Two further topics are handled as separate documents in the repository root rather than as README sections. There is an auth.md for authentication, and a presence.md for adding Presence to track who is online.

Presence is the one to look at if you want the most transferable idea from the tutorial, because it is the standard Phoenix mechanism for tracking which users are connected, and it is the sort of thing a real time application needs long before it needs anything more elaborate.

## The Docker image pins Elixir 1.18.4 and OTP 27.3.4.1

The Dockerfile in the repository root is the most concrete artefact for anyone who wants to reproduce the environment, and it is more opinionated than a typical tutorial Dockerfile.

```dockerfile
ARG ELIXIR_VERSION=1.18.4
ARG OTP_VERSION=27.3.4.1
ARG DEBIAN_VERSION=bullseye-20250610-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
```

The header comment explains the choice of base images, stating that Ubuntu and Debian are used instead of Alpine to avoid DNS resolution issues in production, and giving the specific images the file is based on.

The build sequence is the standard Phoenix release build with the ordering done correctly. The build dependencies are installed, hex and rebar are fetched, MIX_ENV is set to prod, the dependencies are fetched for that environment, the compile-time configuration is copied in before the dependencies are compiled so that a configuration change triggers a recompile, and only then are the priv, lib and assets directories copied and the assets deployed and the project compiled. The release configuration is copied in afterwards, with a comment noting that changes to config/runtime.exs do not require recompiling the code, and finally mix release builds the release.

The order of the config copy is the detail that matters if you copy this Dockerfile. Compiling the dependencies before the compile-time configuration is in place is a common way to end up with a release that does not see the environment it was built for.

The repository also carries a fly.toml for the deployment to Fly.io, a rel directory for the release configuration, priv for the static files and the Ecto migrations, and assets for the front end.

## Conclusion

phoenix-chat-example suits a developer new to Phoenix who wants to see a working real time application before reading the framework documentation, since the tutorial keeps the finished app runnable and covers the parts most examples skip, which are testing, deployment and the rest of what it takes to run an application. It does not suit a team looking for a chat product to deploy, because the deliberate simplifications, plain ES5 client, minimal configuration and skipped Phoenix internals are all teaching choices. Before you start, read the tutorial's own pre-requisites section and check the deployed instance at phoenix-chat.fly.dev, note that the project is published without a declared licence so you should confirm the terms in the repository before reusing its code, and expect the interesting parts to be the channel and the presence tracking rather than the message storage.

## FAQ

### What is dwyl's phoenix-chat-example?

It is a step-by-step tutorial for building, testing and deploying a chat application in Phoenix and Elixir, aimed at complete beginners. It covers the mix phx.new generator, a WebSocket channel, a plain JavaScript front end, an Ecto schema for storing messages, testing and deployment to Fly.io.

### How do I run the finished phoenix chat app?

The README asks you to check the pre-requisites first, then clone the project, install the dependencies and run the app. A live deployment is published at phoenix-chat.fly.dev.

### Does the phoenix chat tutorial teach testing and coverage?

Yes. It runs the generated tests, walks through analysing a channel test, then has a section on what is not tested, with steps to add excoveralls as a development dependency, create a coveralls.json file, run the tests with coverage checking and write a test for the untested function.

### What Elixir and Erlang versions does the Docker image use?

The Dockerfile sets ELIXIR_VERSION to 1.18.4, OTP_VERSION to 27.3.4.1 and DEBIAN_VERSION to bullseye-20250610-slim, using the hexpm/elixir builder image and a Debian runner image. The comment explains that Debian is used rather than Alpine to avoid DNS resolution issues in production.

### How does the tutorial store and replay chat history?

It generates a schema and runs an Ecto migration to create the messages table, inserts messages through the CRUD functions, then loads the existing messages when someone joins the chat and sends them to that client so latecomers see the history.

## Sources

- [dwyl/phoenix-chat-example on GitHub](https://github.com/dwyl/phoenix-chat-example)
- [Issues](https://github.com/dwyl/phoenix-chat-example/issues)
- [Project website](https://phoenix-chat.fly.dev)
- [README](https://github.com/dwyl/phoenix-chat-example/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dwyl-phoenix-chat-example
