Building a Chat Application with Elixir and Phoenix Channels
In the world of real-time applications, building a chat application that can handle multiple users and deliver messages instantly is a common requirement. Elixir, a functional and concurrent programming language, along with the Phoenix web framework, can be a powerful combination to create such applications. In this blog post, we will guide you through the process of building a chat application from scratch using Elixir and Phoenix Channels.
Prerequisites
Before diving into the project, you should have a basic understanding of Elixir and Phoenix. If you’re new to these technologies, it’s recommended to go through some beginner-level tutorials to familiarize yourself with the concepts. Additionally, ensure that you have Elixir and Phoenix installed on your machine.
Setting Up the Project
Let’s start by setting up the basic structure of our chat application.
Step 1: Install Phoenix
If you haven’t installed Phoenix yet, you can do so by following the official installation guide at Phoenix Installation Guide.
Step 2: Create a New Phoenix Project
Open your terminal and run the following command to create a new Phoenix project:
bash mix phx.new chat_app
Step 3: Configure the Database
Navigate to the newly created project folder:
bash cd chat_app
Next, create and migrate the database:
bash mix ecto.create mix ecto.migrate
Step 4: Start the Phoenix Server
Start the Phoenix development server with the following command:
bash mix phx.server
Now, you can visit http://localhost:4000 in your web browser to see the default Phoenix welcome page.
Implementing Authentication
To allow users to join the chat and interact, we need to implement user authentication. We will use Guardian, an authentication library for Elixir, to handle user registration and login.
Step 1: Add Guardian to Dependencies
Open the mix.exs file and add :guardian and :argon2_elixir to the list of dependencies:
elixir
defp deps do
[
# other dependencies...
{:guardian, "~> 2.0"},
{:argon2_elixir, "~> 2.0"}
]
end
Step 2: Install and Compile Dependencies
Run the following command to fetch and compile the new dependencies:
bash mix deps.get mix deps.compile
Step 3: Generate Guardian Secret Key
Generate a new Guardian secret key using the following command:
bash mix guardian.gen.secret
Copy the generated key and add it to your config/dev.exs and config/test.exs files:
elixir config :guardian, Guardian, allowed_algos: ["HS512"], secret_key: "YOUR_GENERATED_SECRET_KEY"
Step 4: Implement User Authentication
Create a new file named user.ex under the lib/chat_app directory and define the user schema and changeset:
elixir
defmodule ChatApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :username, :string
field :email, :string
field :password, :string, virtual: true
field :password_hash, :string
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:username, :email, :password])
|> validate_required([:username, :email, :password])
|> unique_constraint(:email)
|> put_password_hash()
end
defp put_password_hash(changeset) do
case changeset.valid? do
true ->
case changeset.data[:password] do
nil -> changeset
password ->
put_change(changeset, :password_hash, Comeonin.Argon2.hashpwd_salt(password))
end
false -> changeset
end
end
end
Next, create a new migration to add the users table to the database:
bash mix ecto.gen.migration create_users
Open the generated migration file and modify it as follows:
elixir
defmodule ChatApp.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :username, :string
add :email, :string
add :password_hash, :string
timestamps()
end
create unique_index(:users, [:email])
end
end
Now, apply the migration to create the users table:
bash mix ecto.migrate
Step 5: User Registration and Login
Let’s implement user registration and login functionality in the chat application. Add the following code snippets to the corresponding files:
web/router.ex
elixir
defmodule ChatAppWeb.Router do
use ChatAppWeb, :router
# other pipelines...
pipeline :browser_session do
plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"}
plug Guardian.Plug.LoadResource
end
scope "/", ChatAppWeb do
pipe_through :browser
# other routes...
get "/register", UserController, :new
post "/register", UserController, :create
get "/login", SessionController, :new
post "/login", SessionController, :create
delete "/logout", SessionController, :delete
end
end
web/controllers/user_controller.ex
elixir
defmodule ChatAppWeb.UserController do
use ChatAppWeb, :controller
def new(conn, _params) do
render(conn, "new.html")
end
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, _user} ->
conn
|> Guardian.Plug.sign_in(changeset.data)
|> redirect(to: "/")
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
end
web/controllers/session_controller.ex
elixir
defmodule ChatAppWeb.SessionController do
use ChatAppWeb, :controller
def new(conn, _params) do
render(conn, "new.html")
end
def create(conn, %{"session" => session_params}) do
user = Repo.get_by(ChatApp.User, email: session_params["email"])
case Guardian.authenticate(user, session_params["password"]) do
{:ok, user, _claims} ->
conn
|> Guardian.Plug.sign_in(user)
|> redirect(to: "/")
{:error, _reason} ->
conn
|> put_flash(:error, "Invalid email or password")
|> redirect(to: "/login")
end
end
def delete(conn, _params) do
conn
|> Guardian.Plug.sign_out()
|> redirect(to: "/")
end
end
Step 6: Creating a Chat Room
Now that we have user authentication in place, let’s move on to creating a chat room using Phoenix Channels.
Step 1: Generate a Channel
Create a new Phoenix Channel by running the following command:
bash mix phx.gen.channel ChatRoom
This will generate a new channel, along with a test file, migration, and other necessary files.
Step 2: Update the Socket
In lib/chat_app_web/channels/user_socket.ex, add the newly generated chat room channel to the list of allowed channels:
elixir defmodule ChatAppWeb.UserSocket do use Phoenix.Socket # other code... channel "chat_room:*", ChatAppWeb.ChatRoomChannel end
Step 3: Implement the Chat Room Channel
Edit the file lib/chat_app_web/channels/chat_room_channel.ex and update it as follows:
elixir
defmodule ChatAppWeb.ChatRoomChannel do
use Phoenix.Channel
def join("chat_room:" <> room_id, _payload, socket) do
{:ok, socket}
end
def handle_in("new_message", %{"content" => content}, socket) do
broadcast(socket, "new_message", %{
"username" => socket.assigns.current_user.username,
"content" => content
})
{:noreply, socket}
end
end
Step 4: Create the Chat Room Page
Create a new file named chat_room_live.ex in the lib/chat_app_web/live directory with the following content:
elixir
defmodule ChatAppWeb.ChatRoomLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, current_user: socket.assigns.current_user)}
end
def render(assigns) do
~L"""
<div>
<h1>Welcome to the Chat Room, <%= @current_user.username %></h1>
<div id="chat-box">
<%= for message <- @messages do %>
<p><strong><%= message["username"] %>:</strong> <%= message["content"] %></p>
<% end %>
</div>
<form phx-submit="new_message" phx-change="disableButton">
<input type="text" name="content" placeholder="Type your message here..." />
<button id="send-button" disabled="disabled">Send</button>
</form>
</div>
"""
end
def handle_event("new_message", %{"content" => content}, socket) do
ChatAppWeb.Endpoint.broadcast("chat_room:lobby", "new_message", %{
"username" => socket.assigns.current_user.username,
"content" => content
})
{:noreply, assign(socket, messages: [message | socket.assigns.messages])}
end
defp message(%{"username" => username, "content" => content}) do
%{"username" => username, "content" => content}
end
end
Step 5: Update the Router
In the web/router.ex file, add the chat room route:
elixir
defmodule ChatAppWeb.Router do
use ChatAppWeb, :router
# other code...
scope "/", ChatAppWeb do
pipe_through :browser
get "/", PageController, :index
live "/chat", ChatRoomLive
end
end
Conclusion
Congratulations! You’ve successfully built a real-time chat application using Elixir and Phoenix Channels. We covered the basics of user authentication and implemented a chat room using Phoenix Channels and LiveView. Elixir’s concurrent and fault-tolerant nature makes it an excellent choice for building scalable and responsive real-time applications.
Remember, this is just a starting point, and there’s a lot more you can do to enhance your chat application. You can add features like private messaging, user presence tracking, or even group chat rooms. Happy coding!
Remember that this blog is a basic guide to building a chat application. There’s much more you can explore and improve upon, including optimizing performance, adding more features, and handling edge cases. Happy coding!
Table of Contents


