Building a simple Chat app with Elixir and Phoenix
The past few years, more and more applications have been transitioning to websockets
for real-time communication, even forcing some frameworks to implement them (such as
ActionCable in Rails 5). The Phoenix Framework for Elixir implements it
natively, without depending on any external programs such as Redis
.
Today, we’re going to build a super simple chat application in Elixir using the Phoenix Framework. We’re going to ignore authentication, authorization and other features so we can quickly go over the basics, and get websockets in Elixir running.
Getting Started
Start by installing Elixir and Phoenix. Optionally, check out my talk on Introduction to Elixir. Create a new phoenix project and call it whatever you like, and start the server so you can see the app in your browser:
# Create a new Phoenix project called chatroom
$ mix phoenix.new chatroom
# Start the server
$ cd chatroom
$ mix phoenix.server
Creating the View
Let’s start with something easy by writing the markup and CSS for our chat app. Open
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>
</div>
We’ve created an empty div
that will list all chat messages and two text fields (one
for the user’s name and one for the message). Now open web/static/css/app.css
and
paste this at the end:
#message-list {
border: 1px solid #777;
height: 400px;
padding: 10px;
overflow: scroll;
margin-bottom: 50px;
}
Point your browser to localhost:4000
, it should look like this:
Setting up a new Channel
We’re going to create a new channel called lobby
. Open up web/channels/user_socket.ex
and add this line:
channel "lobby", Chatroom.LobbyChannel
Create a new file called web/channels/lobby_channel.ex
and implement the
functionality for the new lobby
channel. The join
method here always returns
{:ok, socket}
to allow all connections to the channel. The handle_in
method is
fired every time a new incoming message is received on the socket, which broadcasts
that message to all other open sockets.
defmodule Chatroom.LobbyChannel do
use Phoenix.Channel
def join("lobby", _payload, socket) do
{:ok, socket}
end
def handle_in("new_message", payload, socket) do
broadcast! socket, "new_message", payload
{:noreply, socket}
end
end
Handling the connections on Client-side
To make things easier, we’ll start by adding jQuery
to our
web/templates/layouts/app.html.eex
:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
Phoenix comes packed with a simple javascript socket client, but it’s disabled
by default. Go into your web/static/js/app.js
and uncomment the last line:
// ...
import socket from "./socket"
Go into your web/static/js/socket.js
and paste in this:
// ...
let channel = socket.channel("lobby", {});
let list = $('#message-list');
let message = $('#message');
let name = $('#name');
message.on('keypress', event => {
if (event.keyCode == 13) {
channel.push('new_message', { name: name.val(), message: message.val() });
message.val('');
}
});
channel.on('new_message', payload => {
list.append(`<b>${payload.name || 'Anonymous'}:</b> ${payload.message}<br>`);
list.prop({scrollTop: list.prop("scrollHeight")});
});
channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) })
.receive("error", resp => { console.log("Unable to join", resp) })
// ...
Here, we listen for a keypress
event on the message text field. Whenever the user
enters a message, it’s pushed on the channel and the text field is cleared. When
there’s an incoming message on the channel, it’s appended to the div we previously
created and scrolled to the bottom.
Going from there
So far we’ve implemented a super simple chat application in Elixir. It’s obviously not perfect and there’s alot of stuff missing. The next logical step would be to add some sort of authentication and authorization, and implement more one-on-one and private chat rooms.
Here are some links you should check out:
- Source of Simple Phoenix Chat on Github
- Guide on Channels on the Official Phoenix website
- Video on Channels by Chris Mccord