Writing an Ember Backend in Phoenix

I like the new Zoey mascot, so I figured I’d give her a neat new shirt!

Last Updated On: 3/31/2016

Current Versions

Elixir: v1.2.2

Phoenix: v1.1.4

Ember-CLI: v2.4.3

Introduction

In a lot of these posts I’ve talked about different ways of integrating various JavaScript frameworks or front-end libraries into Phoenix’s main application flow, but often (especially lately) people are migrating towards building single-page applications in JS frameworks. In this post we’ll take the SuperRentals example from the emberjs.org tutorial (starting at part 1 here) and we’ll build a nice backend behind it with Phoenix!

This tutorial really isn’t going to delve much into the Ember side of things as this post is more focused on the Phoenix side. I may end up poking my head a little more into the Ember side at a later date (or start building a new application in Ember/Phoenix, which is more likely)!

What The Ember App Is Expecting

At the point in the tutorial where they start talking about mirage, we’re going to take a quick detour and instead write our backend in Phoenix. In our Ember.js superrentals app, let’s open up app/models/rental.js:

import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr(),
owner: DS.attr(),
city: DS.attr(),
type: DS.attr(),
image: DS.attr(),
bedrooms: DS.attr(),
});

Here we’re just setting up our base Ember model structure. We’re specifying that each “rental” object needs to have a title, owner, city, type, image, and number of bedrooms. This is a simple example so we’re not worried about specifying the types for each field on our model. Now let’s hop over to our Phoenix application and get started.

Setting Up Our API Structure

We’ll start off by building our actual API. Since we’re just building a JSON API and don’t really need anything fancy, we’re going to build it and skip brunch.

mix phoenix.new superrentals --no-brunch

Answer y to the question about fetching/installing dependencies. Now our app structure has been created so we can jump right into things! Go into the application directory and run mix ecto.create so that our database is ready and waiting for us, and then we’re going to build out the actual API.

We’ll use a JSON generator from Phoenix to build out our /rentals API. We need to create it with a title (string), owner (string), city (string), type (string), image (string), and bedrooms (integer).

mix phoenix.gen.json Rental rentals title:string owner:string city:string type:string image:string bedrooms:integer

We’ll follow the instructions given and open up web/router.ex and add the following line:

resources "/rentals", RentalController, except: [:new, :edit]

However, when we open up our router, we see that there actually isn’t anything piping through the API pipeline! Let’s add a new scope, “/api”, pipe it through the api pipeline, and add our resource line there:

scope "/api", Superrentals do
pipe_through :api
resources "/rentals", RentalController, except: [:new, :edit]
end

Save the changes to your file and then we’ll run mix ecto.migrate to make sure all of our data is caught up. Let’s also make sure our tests are all green before we start making any further modifications and run mix test. Everything should be green! If you load up your Ember app and your Phoenix app, however, nothing will be working yet (since Ember needs to know where to look for our API endpoints). Go back to our Ember application, and create app/adapters/application.js and give it the following contents:

import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:4000',
namespace: 'api'
});

We’re telling Ember that it needs to fetch data from localhost, port 4000 (where Phoenix will run by default) and that we’re specifically wanting it to fetch restful data under the ‘api’ namespace (so every url would be [host]/[namespace]/[resource]).

Open up the app after this and unfortunately things still aren’t working! If you look at the developer console you’ll see messages such as:

XMLHttpRequest cannot load http://localhost:4000/api/rentals. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:4200' is therefore not allowed access.

The good news is this message is expected! By default, applications cannot fetch across servers/hosts/etc without being specially allowed, through the Cross-Origin Resource Sharing policy. We’ll fix this back over in our Phoenix application.

Enabling CORS in Phoenix With Corsica

Adding CORS support and tweaking the policy is dead simple thanks to whatyouhide’s excellent Corsica Phoenix library! To add support for our app, we’ll open up mix.exs and add the following to our deps function:

{:corsica, "~> 0.4"}

And then we’ll run mix do deps.get, compile to make sure it’s loaded into our application. Next, to configure this, we’ll open up lib/superrentals/endpoint.ex and add the Corsica plug right above our Superrentals.Router plug:

  # ...
plug Corsica, origins: "http://localhost:4200"
plug Superrentals.Router
end

Save this file, reload our Phoenix server, and reload our Ember application and the error has gone away! Instead, though, it has been replaced with another warning message:

WARNING: Encountered “data” in payload, but no model was found for model name “datum” (resolved model name using super-rentals@serializer:-rest:.modelNameFromPayloadKey(“data”))

This is because if you open up web/views/rentals.ex you’ll see that the two functions for “show.json” and “index.json” at the top list data as their root elements, not rental(s), which is what Ember-Data is expecting! Ember-Data expects the resource name in plural when you’re returning multiple resources, and the resource name in singular when you’re only returning a single object.

def render("index.json", %{rentals: rentals}) do
%{rentals: render_many(rentals, Superrentals.RentalView, "rental.json")}
end

def render("show.json", %{rental: rental}) do
%{rental: render_one(rental, Superrentals.RentalView, "rental.json")}
end

def render("rental.json", %{rental: rental}) do
%{id: rental.id,
title: rental.title,
owner: rental.owner,
type: rental.type,
image: rental.image,
bedrooms: rental.bedrooms}
end

In the index function, change data to rentals and then in the show function, change the data: atom to rental: and then reload your Ember application and it will all be working again! Well, sort of. There’s no actual data here! That’s just because our database is blank, so we’re okay. Let’s create a seed file to give us some basic data to start with. Open up priv/repo/seeds.exs and add the following:

alias Superrentals.Repo
alias Superrentals.Rental

%Rental{
title: "Grand Old Mansion",
owner: "Veruca Salt",
city: "San Francisco",
type: "Estate",
bedrooms: 15,
image: "https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg"
} |> Repo.insert!

%Rental{
title: "Urban Living",
owner: "Mike Teavee",
city: "Seattle",
type: "Condo",
bedrooms: 1,
image: "https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg"
} |> Repo.insert!

%Rental{
title: "Downtown Charm",
owner: "Violet Beauregarde",
city: "Portland",
type: "Apartment",
bedrooms: 3,
image: "https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg"
} |> Repo.insert!

Next, we’ll run the seeds file with mix run priv/repo/seeds.exs and then refresh our Ember app and we should see all of the data we’re expecting! Finally, let’s fix our 4 failing specs (since we changed the root key from “data” to “rentals”). Open up test/controllers/rental_controller_test.exs and replace “data” in the index test with “rentals”, and then everywhere else “data” shows up, replace it with “rental”. Run mix test, verify all specs are green again, and now we’re done!

Conclusion

Getting Phoenix up and running to support an Ember app is very simple (as is the trend for nearly every Phoenix and ___ combination)! This allows you to build your apps separately from each other and have a true SPA with an amazingly fast backend to support it!

Ember is a lot of fun to work with, especially now in the 2.4.x days, and Phoenix is of course a lot of fun, so hopefully this inspires some of you to go out and do some work in both!

The repo for the Phoenix backend to SuperRentals is here and the repo for the Ember side to SuperRentals is here.

Show your support

Clapping shows how much you appreciated Brandon Richey’s story.