Writing an Ember Backend in Phoenix, Part 2: Using ja_serializer

Brandon Richey
Developers Writing
Published in
7 min readApr 4, 2016

Last Updated On: 3/30/2016

I’m pretty sure none of my workspaces are ever that neat. Throw the pen in the corner, put a bunch of scribbles on the notebook, maybe have the phone askew, and that’s closer to my working experience!

Current Versions

Elixir: v1.2.2

Phoenix: v1.1.4

Ember-CLI: v2.4.3

ja_serializer: v0.8.1

Previous Post In This Series

Writing An Ember Backend In Phoenix, Part 1

Introduction

In the last post in this series, we covered getting an extremely basic version of a Phoenix backend to support the SuperRentals Ember tutorial application! It was very simple to write and a very simple application in general, but if you really wanted to move in step with industry/community standards, it’s missing a few bells and whistles. We’ll address at least one of those to start by making our Phoenix API backend JSON API-compliant! To do this, we’ll incorporate AgilionApps’ ja_serializer library into our SuperRentals application!

Adding ja_serializer

We’ll start off by adding ja_serializer to our Phoenix application. The Github page for ja_serializer has an incredibly detailed installation/configuration page, so we’re going to follow the instructions pretty closely and I’ll try to explain bits and pieces that may seem unclear. Following the instructions from https://github.com/AgilionApps/ja_serializer, we’ll start by adding ja_serializer to our project by adding it to the deps function in mix.exs:

{:ja_serializer, "~> 0.8.1"}

Save the file and then run mix do deps.get, compile in your terminal! Everything should compile just fine, so let’s move on to actually configuring ja_serializer in our app!

Converting Rentals To ja_serializer

Now that we have ja_serializer in our app, it’s time to start integrating it with our application. The first thing we need to do is change from our old default rental views that Phoenix created for us! Open web/views/rental_view.ex and we’ll replace the entire contents of the file with the following:

defmodule Superrentals.RentalView do
use Superrentals.Web, :view
use JaSerializer.PhoenixView
attributes [:title, :owner, :city, :type, :image, :bedrooms]
end

As you can see, lots of boilerplate code has been removed, which is very nice! Next, we’re going to very closely follow the instructions from the Github page for ja_serializer. We’ll start by configuring Phoenix and Plug to allow/specifically look for the “json-api” type. Open config/config.exs:

config :phoenix, :format_encoders,
"json-api": Poison
config :plug, :mimes, %{
"application/vnd.api+json" => ["json-api"]
}

Next, we have to configure our api pipeline to allow the proper content-type and deserialization functions to make our API JSONAPI-compatible! Open up web/router.ex:

pipeline :api do
plug :accepts, ["json", "json-api"]
plug JaSerializer.ContentTypeNegotiation
plug JaSerializer.Deserializer
end

Modify Our Ember Application To Use JSON API

If we just took the codebases from the last post, then our Ember application won’t work quite yet. We have one quick tweak we have to make to our Ember application to allow support for a JSON API-compliant backend. In the Ember Superrentals app, open app/adapters/application.js:

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

We’re just changing the old DS.RESTAdapter.extend line to DS.JSONAPIAdapter.extend (which is also built-in)! That’s it, reload your Ember app and it should now just be working! However, if you open up your Phoenix logs, you’ll see a warning message showing up. Let’s take a closer look at that.

Fixing A Warning in Phoenix App

You may be seeing the following error message in the Phoenix logs:

warning: Passing data via `:model`, `:rentals` or `:rental`
atoms to JaSerializer.PhoenixView has be deprecated. Please use
`:data` instead. This will stop working in a future version.

This is because our Rental Controller is still trying to send data to the render functions with rental as our key, which is deprecated and soon won’t be supported. We like to keep things proper and up-to-date, so let’s head back to our Phoenix application and open up web/controllers/rental_controller.ex. First, modify the call to scrub_params to look for “data” instead of “rental”.

plug :scrub_params, "data" when action in [:create, :update]

In index, change “rentals: rentals” in the render call to “data: rentals”:

def index(conn, _params) do
rentals = Repo.all(Rental)
render(conn, "index.json", data: rentals)
end

In create, change “rental: rental” in render to “data: rental” (as well as in the pattern match for that function to catch data[“attributes”][…]):

def create(conn, %{"data" => %{"attributes" => rental_params}}) do
changeset = Rental.changeset(%Rental{}, rental_params)

case Repo.insert(changeset) do
{:ok, rental} ->
conn
|> put_status(:created)
|> put_resp_header("location", rental_path(conn, :show, rental))
|> render("show.json", data: rental)
# ...

In show, change “rental: rental” in render to “data: rental”:

def show(conn, %{"id" => id}) do
rental = Repo.get!(Rental, id)
render(conn, "show.json", data: rental)
end

In update, change “rental: rental” in render to “data: rental” (and change the pattern matching here too to catch data[“attributes”][…]):

def update(conn, %{"id" => id, "data" => %{"attributes" => rental_params}}) do
rental = Repo.get!(Rental, id)
changeset = Rental.changeset(rental, rental_params)

case Repo.update(changeset) do
{:ok, rental} ->
render(conn, "show.json", data: rental)
# ...

Understanding The Change In Data Format

Before we move on, let’s understand how this changes the way our data is sent back to the client. First, we’ll take a look at how our structure looks before the change.

From:

{
rental: {
id: 1,
title: "",
city: "",
owner: "",
bedrooms: "",
image: "",
type: ""
}
}

Notice that we have a pretty flat structure here; each resource contains all of its parameters at the root level with no real metadata or anything additional to describe the resource (which is pretty standard for REST). Now, we’ll take a look at what is being returned with our JSON API-complaint structure:

To:

{
data: {
id: 1,
type: "rental",
attributes: {
title: "",
city: "",
owner: "",
bedrooms: "",
image: "",
type: ""
}
}
}

Our root element is now “data” instead of whatever the object was. This contains the id for the element (which used to be located at the root object’s structure intermingled with the rest of the attributes) as well as the type (which describes what object it is). Next, we have the attributes map which includes all of the attributes we listed in the JaSerializer view. If we wanted to include inserted_at or updated_at, we’d open up the Rentals view and change the attributes call to something like this:

attributes [:title, :owner, :city, :type, :image, :bedrooms, :inserted_at, :updated_at]

And as a result we’d expect the new structure to be:

{
data: {
id: 1,
type: "rental",
attributes: {
title: "",
city: "",
owner: "",
bedrooms: "",
image: "",
type: "",
inserted_at: "",
updated_at: ""
}
}
}

Fixing Our Tests

We missed a step from the ja_serializer guide and some of our tests are failing! Let’s fix this before we move on in our project. Open up test/controllers/rental_controller_test.exs:

  setup %{conn: conn} do
conn = conn
|> put_req_header("accept", "application/vnd.api+json")
|> put_req_header("content-type", "application/vnd.api+json")
{:ok, conn: conn}
end

test "lists all entries on index", %{conn: conn} do
conn = get conn, rental_path(conn, :index)
assert json_response(conn, 200)["data"]
end

test "shows chosen resource", %{conn: conn} do
rental = Repo.insert! %Rental{}
conn = get conn, rental_path(conn, :show, rental)
assert json_response(conn, 200)["data"] == %{
"id" => "#{rental.id}",
"type" => "rental",
"attributes" => %{
"title" => rental.title,
"owner" => rental.owner,
"city" => rental.city,
"type" => rental.type,
"image" => rental.image,
"bedrooms" => rental.bedrooms
}
}

end
test "creates and renders resource when data is valid", %{conn: conn} do
conn = post conn, rental_path(conn, :create), data: %{ attributes: @valid_attrs }
assert json_response(conn, 201)["data"]["id"]
assert Repo.get_by(Rental, @valid_attrs)
end

test "does not create resource and renders errors when data is invalid", %{conn: conn} do
conn = post conn, rental_path(conn, :create), data: %{ attributes: @invalid_attrs }
assert json_response(conn, 422)["errors"] != %{}
end

test "updates and renders chosen resource when data is valid", %{conn: conn} do
rental = Repo.insert! %Rental{}
conn = put conn, rental_path(conn, :update, rental), data: %{ attributes: @valid_attrs }
assert json_response(conn, 200)["data"]["id"]
assert Repo.get_by(Rental, @valid_attrs)
end

test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
rental = Repo.insert! %Rental{}
conn = put conn, rental_path(conn, :update, rental), data: %{ attributes: @invalid_attrs }
assert json_response(conn, 422)["errors"] != %{}
end

Note the changes to the setup block (adding the new request headers) and the changes to our index and show tests. We need these to reflect the new data and structure changes (I’ve bolded each of the areas you need to modify). Now, run mix test, and all should be back to green!

Conclusion

Overall, ja_serializer is a pretty painless thing to add in to your Phoenix backend to support an Ember application expecting a JSON-API-compliant data structure! The nice thing about using something like JSON API is you remove even more guesswork and non-standard responses from your application, making the API consumption experience for your consumers more delightful and removing the guesswork and bikeshedding of deciding how to structure certain calls! Our application is still insanely simple right now (it’s not much of a CRUD given that we only support the R part from our Ember application), but it’s a good start and we can start building upon this more and more as we go along. Join me for the next couple of tutorials where we’ll start talking about fleshing out our CRUD for rentals and implementing Authentication between our Ember application and our Phoenix application!

Check out my new book!

Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!

--

--