Building a chat app in Eight minutes with Phoenix

Building a chat app in Eight minutes with Phoenix

Spread the love

Phoenix is the most traditional Framework for web pattern in Elixir.

And no doubt one of it’s most scrumptious ingredients is the flexibility so that you just would possibly perhaps add realtime performance, which we can fabricate with Channels.

In this episode let’s leer how can fabricate a uncomplicated chat utility with Phoenix the use of websockets.

Let’s originate this from accomplishing from the flooring-up, so the first element we’ll need is a original Phoenix app – let’s salvage a fresh one with the very unoriginal title ‘chat’.

$ mix phx.fresh chat

And we’ll install the dependencies.

Then we’ll cd into the ‘chat’ checklist and salvage our database:

$ mix ecto.salvage

And let’s originate our app to form distinct that the full lot appears perfect.

$ mix phx.server

And fine – we leer the familiar “Welcome to Phoenix!” web jabber.

Now that we know the full lot is working let’s originate on building our chat room.

We’ll need to generate a fresh channel.

Phoenix presents a generator we can use, we’ll intellectual need to give it a title. Let’s play with the postulate of a chat-room and talk to ours water_cooler

$ mix water_cooler

And it creates a water_cooler_channel.ex module

We’ll leer at it in a minute, nonetheless first let’s add our channel to the particular person socket handler.

We’ll ship any events with the water_cooler topic to our fresh ‘WaterCoolerChannel’

The asterisk is a wildcard, catching any events coming into the water_cooler topic – irrespective of the subtopic – and sending them to our WaterCoolerChannel


defmodule ChatWeb.UserSocket fabricate
  use Phoenix.Socket

  channel "water_cooler:*", ChatWeb.WaterCoolerChannel


With that as much as this point let’s starting up the water_cooler_channel.ex that we generated.

It has a join callback that can handle events for the water_cooler:foyer topic/subtopic.

In this example that Phoenix creates, it’s giving us an approved? feature that can in any respect times return honest. It’s a honorable files for where shall we put authorization logic.

In our example we won’t fabricate any authorization, so let’s capture the logic and salvage it in any respect times return the OK tuple with our socket. And since we won’t be the use of the payload we can ignore it with the underscore.

The handle_in ping feature we wont use, so let’s capture it.

The handle_in instruct feature is accountable for broadcasting any chat events to everyone that’s joined our channel.

Ogle that here’s self-discipline to pattern match on “instruct” – we’ll need to be mindful to use this after we push events out from the client .

And we’ll capture the approved? feature since we eradicated our authorization logic within the join callback .


defmodule ChatWeb.WaterCoolerChannel fabricate
  use ChatWeb, :channel

  def join("water_cooler:foyer", _payload, socket) fabricate
    {:ample, socket}

  #It's also overall to acquire messages from the client and
  #broadcast to everyone in basically the most original topic (water_cooler:foyer).
  def handle_in("instruct", payload, socket) fabricate
    broadcast socket, "instruct", payload
    {:noreply, socket}

Now let’s flow to our sources/js/socket.js

And we leer some code has already been populated that imports and connects our socket to the socket path in our endpoint.ex module which is then handled by our UserSocket.

Our default code also exhibits how we can join channels with a topic. The self-discipline we outlined in our WaterCoolerChannel is water_cooler:foyer so let’s change that here.

It then logs out a message to our browser’s console letting us know if we’ve joined successfully or now no longer.


let channel ="water_cooler:foyer", {})

Then we’ll starting up app.js and import our socket.


import "phoenix_html"

import socket from "./socket"

Now let’s flow to the enlighten line and originate our server.

$ mix phx.server

Let’s give it a shot. We’ll starting up our browser and within the console – we leer our success message is printed.

Now that we’re joining successfully let’s salvage a chat window and originate we’ll use to put up messages with.

But Let’s first customise our bother a piece.

We’ll capture the phoenix.css stylesheet. And within the app.css I’ll paste in some personalized css, which is supplied within the linked GitHub repo.

Then we’ll starting up our app.html.eex template and let’s capture the Phoenix stamp.

Template path: lib/chat_web/templates/structure/app.html.eex


Then in our web jabber’s index.html.eex template we’ll paste within the chat window and and originate that can salvage a username and message field.

Template path: lib/chat_web/templates/web jabber/index.html.eex


The Water Cooler

Then let’s starting up our browser and leer what it appears love.

And we leer our chat field and originate.

Now let’s add some JavaScript to salvage our chat working, so that after we put up a message it’s displayed.

We’ll salvage a fresh file in our sources/js named water_cooler.js

And we’ll salvage our WaterCooler object.

The we’ll salvage a feature init that can elevate our socket.

And with our socket we’ll join the channel with our “water_cooler:foyer” topic. Now that we’ve joined our channel, we have to ship and acquire events.

Let’s salvage a fresh feature listenForChats that can elevate our channel.

I’ll paste in our code for sending events.

But let’s stroll thru this.

We’re salvage our chat originate with the identity chat-originate after which listening for when it’s submitting.

When the originate is submitted we’re combating any additional action.

then we’re getting the username from the originate

And the message from the originate.

Then we’re calling channel.push with “instruct” and payload of our username and the message. This is in a position to perhaps ship our tournament to the server, where it’ll be picked up by our WaterCoolerChannel.

Now that we’re setup to ship events, we have to acquire them.

We’ll use channel.on to subscribe to channel events, matching on “instruct”

Then we’ll salvage our chatBox

And our salvage a fresh message block

Then we’ll fabricate up our message by getting the title and message from the got payload.

We’ll then append that message to our chatBox.


let WaterCooler = {
  init(socket) {
    let channel ='water_cooler:foyer', {})

  listenForChats(channel) {
    doc.getElementById('chat-originate').addEventListener('publish', feature(e){

      let userName = doc.getElementById('particular person-title').worth
      let userMsg = doc.getElementById('particular person-msg').worth

      channel.push('instruct', {title: userName, body: userMsg})

      doc.getElementById('particular person-title').worth = ''
      doc.getElementById('particular person-msg').worth = ''

    channel.on('instruct', payload => {
      let chatBox = doc.querySelector('#chat-field')
      let msgBlock = doc.createElement('p')

      msgBlock.insertAdjacentHTML('beforeend', `${payload.title}: ${payload.body}`)

export default WaterCooler

Now let’s starting up our app.js

And we’ll import WaterCooler

Then we’ll call init, passing in our socket to use.


import WaterCooler from "./water_cooler"


And if we return to our browser and elevate a leer at typing a message.

We leer our message is got and broadcasted out to everyone that’s joined.

And let’s strive sending a message aid – and that works too!

This is fine, nonetheless after we refresh the web jabber – our chat disappears due to it’s now no longer being persisted.

We’ll repair that by saving our chats to the database.

Let’s salvage Phoenix fabricate the heavy lifting for us here and use the context generator to manufacture out the modules and database migration that we’ll need.

We’ll flow to the enlighten line – and we’ll end our server.

And let’s our fresh context ‘Chats’

our ecto schema module will likely be ‘Message’

And our table will likely be messages with a title column that’s a string

and body column that’s a textual jabber.

$ mix phx.gen.context Chats Message messages title:string body:textual jabber

Then we’ll migrate our database.

$ mix ecto.migrate

And let’s elevate a transient leer on the message module that become once created.

We leer our schema with our body and title fields.

and our changeset.

then let’s starting up the chats context

And it’s been populated with some functions to avoid wasting and salvage our messages.

Gigantic, now we have to avoid wasting our messages when somebody posts one within the chat. Let’s fabricate that in our channel.

Now we’ll starting up our water_cooler_channel.ex

Then let’s alias our ‘chats’ context.

And in our handle_in feature we’ll save the message sooner than it’s broadcasted out.

Now shall we form this asynchronous, nonetheless let’s support it uncomplicated for the functions of this episode.


alias Chat.Chats
def handle_in("instruct", payload, socket) fabricate
  broadcast socket, "instruct", payload
  {:noreply, socket}

Now that we’re saving our messages we have to load any novel ones into the chat.

Let’s starting up our page_controller.ex

We’ll alias our Chat.Chats module

Then let’s salvage all our messages

And we’ll flow them in our assigns.


defmodule ChatWeb.PageController fabricate
  use ChatWeb, :controller

  alias Chat.Chats

  def index(conn, _params) fabricate
    messages = Chats.list_messages()
    render conn, "index.html", messages: messages

Now we can starting up our index.html.eex template and render any messages that had been returned in our chat field.

Template path: lib/chat_web/templates/web jabber/index.html.eex


The Water Cooler

<%= for message <- @messages do %>

<%= %>: <%= message.body %>

<% end %>

Then let’s restart our server.

And return to the browser and put up some messages.

Then if we fabricate a transient take a look at of the database – we leer our messages are there.

And if we return to our chat and refresh the web jabber – we leer our novel messages are loaded.

files image
Be taught Extra

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *