Phoenix ships with tasks to generate resources for us. These tasks are:

The one we’re really interested here is phoenix.gen.json .

Let’s create a new resource called posts :

$ mix phoenix.gen.json Post posts title:string content:string

This will generate:

A model web/models/post.ex .

A model test file test/models/post_test.exs .

A migration file in priv/repo/migrations/20150516125431_create_post.exs

A post view web/views/post_view.ex .

A changeset view web/views/changeset_view.ex .

A controller web/controllers/post_controller.ex .

A controller test file in test/controllers/post_controller_test.exs .

It will also prompt you with a few instructions to update the router web/router.ex and to run the migrations. Let’s do that now.

Open web/router.ex and change it to this:

defmodule RestApi.Router do use RestApi.Web, :router pipeline :api do plug :accepts, ["json"] end scope "/", RestApi do pipe_through :api scope "/v1", V1, as: :v1 do resources "/posts", PostController end end end

We got rid of a lot of code we’re not using for an API. We added the resource "/posts" within the "/v1" scope and we setup the :api pipeline within "/" scope.

Let’s run phoenix.routes to list our routes:

$ mix phoenix.routes ... Generated rest_api app v1_post_path GET /v1/posts RestApi.V1.PostController.index/2 v1_post_path GET /v1/posts/:id/edit RestApi.V1.PostController.edit/2 v1_post_path GET /v1/posts/new RestApi.V1.PostController.new/2 v1_post_path GET /v1/posts/:id RestApi.V1.PostController.show/2 v1_post_path POST /v1/posts RestApi.V1.PostController.create/2 v1_post_path PATCH /v1/posts/:id RestApi.V1.PostController.update/2 PUT /v1/posts/:id RestApi.V1.PostController.update/2 v1_post_path DELETE /v1/posts/:id RestApi.V1.PostController.delete/2

Routes all set, next thing is to create our development and test database and run the migration:

$ mix ecto.create ... The database for RestApi.Repo has been created. $ mix ecto.migrate [info] == Running RestApi.Repo.Migrations.CreatePost.change/0 forward [info] create table posts [info] == Migrated in 0.1s

If you’re seeing an error while executing any of these commands, you need to make sure you have PostgreSQL running and you have a role "postgres" with password "postgres" created. If you want to change the database configuration you can change it in config/dev.ex line 32-33 and config/test.ex line 15-16.

Next we need to version our controllers and views and make some additional modifications. First let’s create some folders and move the files:

$ mkdir -p web/controllers/v1 $ mv web/controllers/post_controller.ex web/controllers/v1 $ mkdir -p web/views/v1 $ mv web/views/post_view.ex web/views/v1 $ mkdir -p test/controllers/v1 $ mv test/controllers/post_controller_test.exs test/controllers/v1/

Add V1 to each module name we’re versioning like this:

# web/controllers/v1/post_controller.ex defmodule RestApi.V1.PostController ... end # test/controllers/v1/post_controller_test.exs defmodule RestApi.V1.PostControllerTest do ... end # web/views/v1/post_view.ex defmodule RestApi.V1.PostView do ... end

If you run the test now, you’ll see an error like this:

$ mix test ... ** (CompileError) test/controllers/v1/post_controller_test.exs:14: function post_path/2 undefined (stdlib) lists.erl:1336: :lists.foreach/2 (stdlib) erl_eval.erl:657: :erl_eval.do_apply/6

This is because the router helper post_path/2 doesn’t exists. When we setup our resource within the "/v1" scope, we specified the option as: :v1 which adds the prefix to the router helper post_path/2 .

To fix this, we need to replace post_path calls for v1_post_path in test/controllers/v1/post_controller_test.exs .

Once fixed, if you run the test again, you should see four failing tests with the following error:

... ** (UndefinedFunctionError) undefined function: RestApi.PostView.__resource__/0 (module RestApi.PostView is not available) ...

This seems like a bug at first, but then I looked through the source code and I found that the issue is because we’re calling render_many/2 and render_one/2 in web/views/v1/post_view.ex .

If you follow the code, you’ll notice that Phoenix.View.render_many/2 and Phoenix.View.render_one/2 call Phoenix.View.render_many/3 and Phoenix.View.render_one/3 respectively and these two functions call Phoenix.View.view_for_model/1 which inflects the view for a model and does not considering our versioning model.

I gotta admit, I pulled my hair for a few minutes over this but then I found the solution thanks to Chris McCord.

To fix it you need to call Phoenix.View.render_many/3 and Phoenix.View.render_one/3 respectively instead and pass the View module name like this:

defmodule RestApi.V1.PostView do use RestApi.Web, :view def render("index.json", %{posts: posts}) do %{data: render_many(posts, RestApi.V1.PostView, "post.json")} end def render("show.json", %{post: post}) do %{data: render_one(post, RestApi.V1.PostView, "post.json")} end def render("post.json", %{post: post}) do %{id: post.id} end end

This will fix the specs:

$ mix test ... Generated rest_api app ............ Finished in 1.1 seconds (0.9s on load, 0.2s on tests) 12 tests, 0 failures Randomized with seed 848797

There you have it, a simple REST API built with Phoenix Framework and Elixir.