Building a Phoenix API

After reading some tutorials about building APIs with Phoenix Framework, I saw that most of the posts are out of date. So, I decided to write a blog post that covers how to build a JSON API using Elixir + Phoenix from scratch.

We’ll build a simple market API called market_api with 2 models: Market and Product.

I will try to maintain the stack updated and if you are reading this and these are not the latest, let me know and I’ll update this tutorial accordingly. We are using:

  • Elixir: 1.1.1

  • Phoenix: 1.1.2

  • Ecto: 1.1.1

  • Corsica: 0.4

  • Ja_serializer: 0.6.0

Installing Phoenix

Best instructions for installing Phoenix can be found on the Phoenix website.

Creating the Phoenix app

To get started, we need to generate our application using Phoenix mix task. We do this using mix phoenix.new [project_name] [command]

mix phoenix.new market_api --no-brunch

We don’t need brunch to compile assets because we have none. Then press [Yn] to install all dependencies.

Next we need to setup our database in config/dev.exs. You should see some configurations at the bottom of the file:

# Configure your database
config :market_api, MarketApi.Repo,,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "pxblog_dev",
  hostname: "localhost",
  pool_size: 10

Just change the username and password, then run mix ecto.create to create our database. Now you can run mix phoenix.server and visit http://localhost:4000 and see the Phoenix welcome page.

Creating the models

The first thing we’ll do is utilize one of Phoenix’s generators to build our resource. We need to generate JSON resources, in Phoenix we can use the mix task mix phoenix.gen.json (docs: http://hexdocs.pm/phoenix/Mix.Tasks.Phoenix.Gen.Json.html)

Run:

mix phoenix.gen.json Market markets name:string phone:string

and:

mix phoenix.gen.json Product products name:string barcode:string image:string price:decimal market_id:references:markets

Inside of web/models/market.ex:

has_many :products, MarketApi.Product

and add the following code inside of web/router.ex with:

# Other scopes may use custom stacks.
scope "/api", MarketApi do
  pipe_through :api

  resources "/markets", MarketController, except: [:new, :edit] do
    resources "/products", ProductController, only: [:index]
  end

  resources "/products", ProductController, except: [:new, :edit]
end

This will create our API routes, then we can run mix ecto.migrate to create the database tables.

Seeding the database

We need some data to test the API. Add the following lines to priv/repo/seed.exs:

alias MarketApi.Repo
alias MarketApi.Market
alias MarketApi.Product

[
  %Market{
    name: "Market 1",
    phone: "5555555"
  },
  %Market{
    name: "Market 2",
    phone: "444444"
  },
  %Market{
    name: "Market 3",
    phone: "333333"
  }
] |> Enum.each(&Repo.insert!(&1))



products = [
  %{
    name: "Macbook Pro",
    barcode: "23123AAA",
    image: "http://www.image.foo/1.jpg",
    price: Decimal.new(6000)
  },
  %{
    name: "Headset",
    barcode: "TTTYY3AAA",
    image: "http://www.image.foo/2.jpg",
    price: Decimal.new(50)
  },
  %{
    name: "Printer",
    barcode: "7676763AAA",
    image: "http://www.image.foo/3.jpg",
    price: Decimal.new(200)
  },
  %{
    name: "Pendrive",
    barcode: "2FFAAA",
    image: "http://image.foo/4.jpg",
    price: Decimal.new(10)
  },
]

Repo.transaction fn ->
  Repo.all(Market) |> Enum.each(fn(market) ->
    Enum.each(products, fn(product) ->
      new_product = Ecto.build_assoc(market, :products, Map.put(product, :market_id, market.id))
      Repo.insert!(new_product)
    end)
  end)
end

And run mix run priv/repo/seeds.exs to create and insert the data into the database. Now run the server with mix phoenix.server and curl http://localhost:4000/api/markets or curl http://localhost:4000/api/products and you will see the JSON data.

Structure our data with JSON-API protocol

To do this, we need to use a library called ja_serializer. Add the following line inside of mix.exs

{:ja_serializer, “~> 0.6.0”}

and run mix deps.get.

add use JaSerializer.PhoenixView to the web/web.exs.

def view do
    quote do
      use Phoenix.View, root: "web/templates"
      use JaSerializer.PhoenixView

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import MarketApi.Router.Helpers
      import MarketApi.ErrorHelpers
      import MarketApi.Gettext
    end
  end

Add the following lines inside the web/views/market_view.ex:

attributes [:id, :name, :phone]

has_many :products, link: “/markets/:id/products”

and inside web/views/product_view.ex:

attributes [:id, :name, :barcode, :image, :price, :market_id]

Add following to your config/config.exs:

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

and run the following to recompile plug (See: http://hexdocs.pm/plug/Plug.MIME.html)

touch deps/plug/mix.exs  
mix deps.compile plug

Then change your API pipeline in web/router.ex to:

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

Now run mix phoenix.server, and call the API with the following command:

curl -H "Accept: application/vnd.api+json" http://localhost:4000/api/markets

And you should see the market’s JSON formatted to json-api. These steps are self-explained what we’ve done so far is add the attributes that will be serialized and added the relationship with the product model. (See: http://jsonapi.org/format/#fetching-relationships).

Setting up CORS

We’ll need to setup CORS to make your browser recognize our cross-origin resource, we will set the CORS header in our Phoenix application, we can use a library called Corsica.

Just add this line into you mix.exs file:

{:corsica, "~> 0.4"}

And then run mix deps.get to install the dependencies.

Then add the following line:

plug Corsica, origins: "*"

in *lib/market_api/endpoint.exs, *just before:

plug MarketApi.Router

Creating resources

To test our POST requests we can use curl, in you terminal:

curl -i -H "Accept: application/vnd.api+json" -H 'Content-Type:application/vnd.api+json' -X POST -d '{"market":{"name":"My Market", "phone":"123321"}}' [http://localhost:4000/api/markets](http://localhost:4000/api/markets)

And we will receive the following response:

HTTP/1.1 201 Created
server: Cowboy
date: Sun, 03 Jan 2016 00:46:11 GMT
content-length: 195
content-type: application/json; charset=utf-8
cache-control: max-age=0, private, must-revalidate
x-request-id: 48fa2shdrgkon0k1g5m7ok25vlrpe580
location: /api/markets/49

{“jsonapi”:{“version”:”1.0"},”data”:{“type”:”market”,”relationships”:{“products”:{“links”:{“related”:”/markets/49/products”}}},”id”:”49",”attributes”:{“phone”:123321,”name”:”My Market”,”id”:49}}}%

What’s next?

So, this is a simple getting started, there are many parts that we left behind, like authentication and tests. I’ll try to cover this in the next posts. You can also check out my repo on github.