Teaching ember-cli to talk to phoenix sockets

July 4th, 2015

I ran into a little trouble hooking up websockets when playing with an ember-cli front end app and a phoenix backend server recently. I thought I’d document what I did to get them working for others trying this out. I make no claims that this is the best way to do things but it’s working for me so far. If you know a better way please let me know in the comments or by email!

I used version 0.14.0 of Phoenix and 0.2.7 of ember-cli. This means this post will most likely be out of date at some point but I hope it proves useful to enough people in the meantime.

Background

Phoenix provides a javascript client for its channels allowing you to push data between the client and server in any direction. It’s got a nice API and I really wanted to play with it.

I wanted to create two separate applications, the Ember frontend and the Phoenix backend, each responsible for their own assets and builds. This is so they can be developed and deployed independently without the frontend or backend developer having to know too much about the other.

It’s certainly possible to have ember-cli build it’s output into web/static in the Phoenix app but that would block other interesting deploy strategies for the frontend like S3 via ember-cli-deploy.

The “problem”

Phoenix’s javascript client is written in ES2015, the latest version of Javascript, and this isn’t supported by browsers so you must use a transpiler to make it work. ember-cli doesn’t transpile files in vendor by default so you can’t just throw it in there unfortunately.

There are some Phoenix packages on npm but nothing with a recent enough version of the Phoenix js code. I don’t know enough about the node ecosystem to update them or create one of my own.

Setup

I’m assuming you’ve a ~/scratch folder where the two apps will live, and that you’ve got both Phoenix and ember-cli installed.

The Phoenix backend

You’re going to create a simple Phoenix app with one channel. The generated code handles joins so will be enough to show that everything is working.

$ cd ~/scratch

# answer Y to installing dependencies here
$ mix phoenix.new backend
$ cd backend

# compile the generated app to get the phoenix generators
$ mix

# create our channel
$ mix phoenix.gen.channel Room rooms

web/router.ex

The command output will tell you to put the following in web/router.ex so go do that in your favourite editor.

It can go anywhere inside the module.

socket "/ws", Backend do
  channel "rooms:lobby", RoomChannel
end

see the full version of the file here

Then run the Phoenix server on it’s default port 4000 and move onto the front end.

$ mix phoenix.server

The ember-cli frontend

Lets generate the skeleton app:

$ cd ~/scratch
$ ember new frontend
$ cd frontend
$ ember new route lobby

This means when ember serve is running you can hit http://localhost:4200/lobby and have a look in the console to check out connection was made.

Don’t run the server just yet though, next is getting the Phoenix client js and installing and configuring the pieces to compile it. The version in the generated Phoenix app has already been processed by brunch so you need to download the original ES2015 source. If you’re doing this with a later version of Phoenix than 0.14.0 change the tag in the url below.

$ mkdir vendor/phoenix
$ curl https://raw.githubusercontent.com/phoenixframework/phoenix/v0.14.0/web/static/js/phoenix.js > vendor/phoenix/phoenix.js

ember-cli uses Broccoli as an asset processor so some extra packages are needed for everything to work.

$ npm install broccoli-es6modules --save-dev
$ npm install broccoli-babel-transpiler --save-dev
$ npm install broccoli-merge-trees --save-dev

Brocfile.js

Next edit your Brocfile.js to require and use what just got installed.

Add:

var ES6Modules = require('broccoli-es6modules');
var esTranspiler = require('broccoli-babel-transpiler');
var mergeTrees = require('broccoli-merge-trees');

Just under:

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

at the top of the file.

Next configure transpiling phoenix.js by replacing:

module.exports = app.toTree();

at the bottom of the file with:

var phoenixTree = "./vendor/phoenix";
var phoenixAmdFiles = new ES6Modules(phoenixTree, {
  format: 'amd',
  esperantoOptions: {
    strict: true,
    amdName: "phoenix"
  }
});
var phoenixTranspiledFiles = esTranspiler(phoenixAmdFiles, {});

module.exports = mergeTrees([app.toTree(), phoenixTranspiledFiles]);

see the full version of the file here

app/index.html

The next file to edit is app/index.html. The configuration above produces a transpiled phoenix.js file that needs included in the html so add:

<script src="phoenix.js"></script>

Just underneath

<script src="assets/vendor.js"></script>

You should now be able to run ember serve and go to http://localhost:4200/lobby. You won’t see anything but make sure there are no errors in the browser console.

See the full version of the file here.

Connecting the two apps

So now you’ve the Phoenix javascript in our Ember app lets prove everything is working by connecting the two together. A connection can be made in an Ember initializer that outputs something to the console so you can see it’s working.

$ ember generate initializer backend

app/initializers/backend.js

Edit the generated app/initializers/backend.js file and add the import to the top of the file:

import {Socket} from "phoenix"

Then replace the comment in the initialize function with:

let socket = new Socket("ws://localhost:4000/ws");
socket.connect();
let chan = socket.chan("rooms:lobby", {});
chan.join().receive("ok", () => {
  console.log("Welcome to Phoenix Chat!");
});

See the full version of the file here.

Run ember serve again and in the browser console you should see:

Welcome to Phoenix chat!

In the mix phoenix.server terminal window the output will look like:

[info] GET /ws
[info] JOIN rooms:lobby to Backend.RoomChannel
  Transport:  Phoenix.Transports.WebSocket
  Parameters: %{}
[info] Replied rooms:lobby :ok

CONGRATULATIONS the frontend and backend are talking to each other via websockets.

Conclusions

I don’t know nearly enough about the various asset management and build tools in the Javascript ecosystem to even know if this is a good idea or not. It will allow you to get up and running with Phoenix channels though and if something better comes along you shouldn’t have to change the actual channel code you write.

I’m not going to go into details of how to actually use Phoenix channels in an Ember app. This is mainly because I haven’t figured it out yet but also because this post is long enough!