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.
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.
Let’s start by setting up the basic structure of our chat application.
If you haven’t installed Phoenix yet, you can do so by following the official installation guide at Phoenix Installation Guide.
Open your terminal and run the following command to create a new Phoenix project:
bash mix phx.new chat_app
Navigate to the newly created project folder:
bash cd chat_app
Next, create and migrate the database:
bash mix ecto.create mix ecto.migrate
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.
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.
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
Run the following command to fetch and compile the new dependencies:
bash mix deps.get mix deps.compile
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"
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
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
Now that we have user authentication in place, let’s move on to creating a chat room using Phoenix Channels.
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.
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
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
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
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
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!
In today’s digital landscape, security is paramount when developing mobile applications. As an Ionic developer, it’s crucial to ensure that your app’s user data remains protected from unauthorized access and potential breaches. Firebase Authentication offers a reliable solution to bolster the security of your Ionic app effortlessly. This blog post will guide you through the...
In the realm of web development, combining the strengths of various tools and frameworks can lead to exceptional results. When it comes to building interactive and responsive websites, jQuery and Bootstrap stand as two of the most influential players. In this guide, we’ll delve into the synergy between jQuery and Bootstrap, exploring how their collaboration...
As the demand for AI developers continues to surge in the rapidly evolving tech landscape, finding skilled professionals becomes pivotal for staying at the forefront of innovation. This is because most developers are not well-skilled enough to develop projects that are yet in the minds of business owners and companies as ideas. This is essentially...