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.
We want to work with you. Check out our "What We Do" section!