I’m currently building a Phoenix backend API that is being consumed by Ember Data 1.13 which uses JSON API. Here is how I hooked everything up.
Configuration
You first need to tell Phoenix that it should accept json-api
format.
I created a new
pipeline/2
for the API:
# web/router.ex
pipeline :api do
plug :accepts, ["json-api"]
end
We next need to add the corresponding MIME type:
# config/config.exs
config :plug, :mimes, %{
"application/vnd.api+json" => ["json-api"]
}
Now we have to force Plug to recompile. To do this we have to touch
a
file in the dependency. This may seem a little odd but the documentation
recommends this.
> touch deps/plug/mix.exs
> mix deps.compile plug
I like to namespace my APIs so I created a scope and piped it through my
api
pipeline:
# web/router.ex
scope "api/v1", MyApp do
pipe_through :api
end
I declare all of my API routes in that scope.
Emitting
To emit json-api responses I am currently using
ja_serializer. The
author has indicated that some big changes are likely to better align
with Phoenix conventions but for now this is the only serializer I am
aware of. The README explains how to serialize, it is pretty simple. For
example, to serialize a collection form an index
action:
def index(conn, _) do
foos = Repo.all(Foo)
|> MyApp.FooSerializer.format(conn)
json(conn, foos)
end
You may wish to use Views but I’ve opted not to.
The serializer itself would look like:
# serializers/foo_serializer.ex
defmodule MyApp.FooSerializer do
use JaSerializer
serialize "foo" do
attributes [
"name",
"description"
]
end
end
You can embed relationships as well, check out the project for more deatils.
Consuming
Ember Data not only expects to get JSON API format but also sends JSON
API format back to the server when you are creating or updating. The
attributes keys come in hyphenated and everythig is nested under "data" => "attributes"
. Here is how I made life easier for mmyself.
I first wrote a new plug that deserializes all params that are coming in by forcing hyphenated keys to underscore format. It works recursively on all keys and produces a new params object that is Phoenix friendly. Here is the code:
# web/plugs/deserialize.ex
defmodule MyApp.DeserializePlug do
def init(options) do
options
end
def call(%Plug.Conn{params: %{"format" => "json-api"}, method: "POST"}=conn, _opts) do
result = _deserialize(conn)
end
def call(%Plug.Conn{params: %{"format" => "json-api"}, method: "PUT"}=conn, _opts) do
_deserialize(conn)
end
def call(%Plug.Conn{params: %{"format" => "json-api"}, method: "PATCH"}=conn, _opts) do
_deserialize(conn)
end
def call(conn, _opts), do: conn
defp _deserialize(%Plug.Conn{}=conn) do
Map.put(conn, :params, _deserialize(conn.params))
end
defp _deserialize(%{}=params) do
Enum.into(params, %{}, fn({key, value}) -> { _underscore(key), _deserialize(value) } end)
end
defp _deserialize(value), do: value
defp _underscore(key), do: String.replace(key, "-", "_")
end
I then added this custom plug to my api
pipeline:
# web/router.ex
pipeline :api do
plug :accepts, ["json-api"]
plug MyApp.DeserializePlug
end
Next, JSON API’s schema is verbose and I didn’t want to have to deal with this in my actions,
Elixir’s pattern matching is perfect for this. I capture all the
attributes into params
:
def create(conn, %{"data" => %{"attributes" => params}, "type" => "json-api"}) do
# create action now has
# a "params" object with all the
# attribute date from the client request
end
We can even guard the action for JSON API specific types.
Conclusion
Getting JSON API working with Phoenix takes a few hoops to jump through, but hopefully this helps you get up and running.