Introduction to Phoenix and Elixir

If you’re venturing into the world of real-time web applications, you’re in the right place. Elixir and Phoenix are a dynamic duo that can help you build a robust and scalable chat application. In this article, we’ll dive into the nitty-gritty of creating a chat app using these powerful tools.

Why Elixir and Phoenix?

Elixir, with its asynchronous programming model, is perfect for handling concurrent requests, making it an ideal choice for real-time applications. Phoenix, built on top of Elixir, provides a robust framework for web development, including excellent support for WebSockets and PubSub (Publish/Subscribe) functionality.

Setting Up Your Project

To get started, you need to have Elixir and Phoenix installed on your machine. Here’s how you can create a new Phoenix project:

mix phx.new chatter --no-ecto
cd chatter
mix deps.get
mix ecto.create # If you decide to use Ecto, otherwise skip this step

The --no-ecto flag is used if you don’t want to include the Ecto database library in your project. However, for a more complete chat application, you’ll likely want to include Ecto to store chat messages persistently.

Creating the Channel

In Phoenix, channels are used to handle real-time communication. Let’s create a channel for our chat room:

mix phx.gen.channel chat_room

This command generates the necessary files for your channel. You’ll need to add the channel to your user_socket.ex file:

defmodule ChatterWeb.UserSocket do
  use Phoenix.Socket

  channel "chat_room:lobby", ChatterWeb.ChatRoomChannel
  # ...
end

The chat_room:lobby topic allows clients to subscribe to the channel, similar to how a path in a web request directs to a specific controller.

Setting Up the Channel Module

Open the generated chat_room_channel.ex file and add the necessary functions to handle messages:

defmodule ChatterWeb.ChatRoomChannel do
  use ChatterWeb, :channel

  def join("chat_room:lobby", _params, socket) do
    {:ok, socket}
  end

  def handle_in("new_msg", %{"body" => body}, socket) do
    broadcast(socket, "new_msg", %{body: body})
    {:noreply, socket}
  end
end

This code sets up the channel to join and broadcast new messages.

Creating the View

Now, let’s create the HTML template for our chat app. Open lib/chatter_web/templates/page/index.html.eex and replace its contents with:

<div id='message-list' class='row'>
</div>

<div class='row form-group'>
  <div class='col-md-3'>
    <input type='text' id='name' class='form-control' placeholder='Name' />
  </div>
  <div class='col-md-9'>
    <input type='text' id='message' class='form-control' placeholder='Message' />
  </div>
  <button id='send-btn'>Send</button>
</div>

<script>
  let socket = new Phoenix.Socket("/socket", {params: {token: window.userToken}});
  socket.connect();

  socket.onConnect(() => console.log("Connected"));
  socket.onDisconnect(() => console.log("Disconnected"));

  socket.onMessage("new_msg", payload => {
    let messageList = document.getElementById('message-list');
    let message = document.createElement('div');
    message.innerText = payload.body;
    messageList.appendChild(message);
  });

  document.getElementById('send-btn').addEventListener('click', () => {
    let message = document.getElementById('message').value;
    socket.push("new_msg", {body: message});
    document.getElementById('message').value = '';
  });
</script>

This script sets up a basic chat interface and connects to the Phoenix socket.

Adding CSS

To make our chat app look a bit more presentable, add some CSS to assets/css/app.css:

#message-list {
  border: 1px solid #777;
  height: 400px;
  padding: 10px;
  overflow: scroll;
  margin-bottom: 50px;
}

Using Phoenix LiveView for Real-Time Updates

For a more advanced and seamless real-time experience, you can use Phoenix LiveView. Here’s how you can set it up:

Project Setup with LiveView

If you’re starting from scratch, create a new Phoenix project with LiveView support:

mix phx.new liveview_chat --no-ecto
cd liveview_chat
mix deps.get

Creating the LiveView Module

Generate the necessary files for your LiveView:

mix phx.gen.live Chat ChatLive.Chat

Edit lib/liveview_chat_web/router.ex to add a route for your chat page:

defmodule LiveviewChatWeb.Router do
  use LiveviewChatWeb, :router

  scope "/", LiveviewChatWeb do
    pipe_through :browser

    live "/chat", ChatLive.Chat
  end
end

Setting Up the LiveView Controller

In lib/liveview_chat_web/live/chat_live.ex, define the mount and handle_event functions:

defmodule LiveviewChatWeb.ChatLive.Chat do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :messages, [])}
  end

  def handle_event("send", %{"text" => text}, socket) do
    messages = socket.assigns.messages ++ [text]
    {:noreply, assign(socket, :messages, messages)}
  end

  def render(assigns) do
    ~H"""
    <div id='message-list'>
      <%= for message <- @messages do %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div class='row form-group'>
      <div class='col-md-9'>
        <input type='text' id='message' class='form-control' placeholder='Message' />
      </div>
      <button phx-click='send' phx-value-text='#{gettext("Send")}' >Send</button>
    </div>
    """
  end
end

Handling Events and Rendering

The handle_event function handles the “send” event, and the render function renders the messages.

Persistence Layer

To make your chat app more robust, you’ll want to add a persistence layer. Here’s how you can do it using Ecto:

Generating the Schema and Migration

Generate the schema and migration for your messages:

mix phx.gen.schema Message messages name:string message:string
mix ecto.migrate

Updating the Schema

Update the lib/liveview_chat/message.ex file to include functions for creating and listing messages:

defmodule LiveviewChat.Message do
  use Ecto.Schema
  import Ecto.Changeset

  schema "messages" do
    field :name, :string
    field :message, :string

    timestamps()
  end

  def changeset(message, attrs) do
    message
    |> cast(attrs, [:name, :message])
    |> validate_required([:name, :message])
  end

  def create_message(attrs) do
    %Message{}
    |> changeset(attrs)
    |> LiveviewChat.Repo.insert()
  end

  def list_messages do
    LiveviewChat.Repo.all(Message)
  end
end

Integrating with LiveView

Update your LiveView module to use the persistence layer:

defmodule LiveviewChatWeb.ChatLive.Chat do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    messages = LiveviewChat.Message.list_messages()
    {:ok, assign(socket, :messages, messages)}
  end

  def handle_event("send", %{"text" => text, "name" => name}, socket) do
    case LiveviewChat.Message.create_message(%{name: name, message: text}) do
      {:ok, _message} ->
        messages = LiveviewChat.Message.list_messages()
        {:noreply, assign(socket, :messages, messages)}
      {:error, _changeset} ->
        {:noreply, socket}
    end
  end

  def render(assigns) do
    ~H"""
    <div id='message-list'>
      <%= for message <- @messages do %>
        <div><%= message.name %>: <%= message.message %></div>
      <% end %>
    </div>

    <div class='row form-group'>
      <div class='col-md-3'>
        <input type='text' id='name' class='form-control' placeholder='Name' />
      </div>
      <div class='col-md-6'>
        <input type='text' id='message' class='form-control' placeholder='Message' />
      </div>
      <button phx-click='send' phx-value-text='#{gettext("Send")}' phx-value-name='#{gettext("Name")}' >Send</button>
    </div>
    """
  end
end

Sequence Diagram for Real-Time Messaging

Here’s a sequence diagram to illustrate the flow of real-time messaging using Phoenix and LiveView:

sequenceDiagram participant Client participant Server participant DB Note over Client,Server: User sends a message Client->>Server: WebSocket push event ("send") Server->>Server: Handle event, create message Server->>DB: Insert message into database DB->>Server: Message inserted successfully Server->>Client: Broadcast message to all connected clients Client->>Client: Update message list

Conclusion

Building a chat application with Phoenix and Elixir is a rewarding experience that leverages the strengths of both technologies. From setting up channels and LiveView to adding a persistence layer, this guide has walked you through the key steps to create a robust and real-time chat app. Whether you’re a seasoned developer or just starting out, Phoenix and Elixir offer a powerful combination for building scalable and interactive web applications.

Happy coding, and remember, in the world of real-time web development, every message counts