Phoenix is great for JSON APIs.
Why use a full-featured framework like Phoenix for a JSON API?
Unlike Rails and various other frameworks it inspired, Phoenix carries very little performance overhead beyond what a simpler library would offer. Rails is considerably slower than Rack or Sinatra for a simple app. The same is true of Django compared to simpler libraries like Flask. In contrast, Phoenix apps can serve nearly the same number of requests on the same hardware as apps written with just Plug (which it uses internally).
Since Phoenix comes with a lot of useful things out of the box (routing, controllers, generators, etc, etc, etc) and the performance cost is negligible, most Elixir devs use it even when writing a pure JSON API with a separate web or mobile front-end.
Getting started
This lesson, we'll make the simplest possible API—a single endpoint that doesn't rely on any stored data. We'll make an endpoint /api/
that returns the result of a die roll. First, generate an app. Call it "firehose". If you haven't installed Elixir yet, see the episode on ASDF.
mix phx.new firehose
Let the installer automatically install all the dependencies, cd
into the newly created firehose directory and then take a look at the lib/firehose_web/router.ex
file. There's a block of code that defines a scope for an API. Uncomment that and add a route for /roll
:
scope "/api", FirehoseWeb do
pipe_through :api
get "/roll", RollController, :index # this won't work until we write a controller!
end
The line with pipe_through :api
will call all of the plugs in the :api
pipeline near the top of the file. Unlike the much longer browser pipeline that includes plugs to handle sessions, flash messages and a variety of security related concerns, the api pipeline only has a single plug:
pipeline :api do
plug :accepts, ["json"]
end
The controller
If you start the sever with mix phx.server
, save the file and visit localhost:4000/api/roll, you'll see an error since the route we defined above routes get requests for that route to the :index
action of a RollController
that doesn't exist! Create a new one at /lib/firehose_web/controllers/roll_controller.ex
with the following code:
defmodule FirehoseWeb.RollController do
use FirehoseWeb, :controller
def index(conn, _params) do
num = :rand.uniform(6)
render(conn, "index.json", roll: num)
end
end
The use FirehoseWeb, :controller
line at the top pulls in functionality via a macro and makes the module a Phoenix controller.
The index/2
function takes a connection struct and parameters (which aren't used) and returns the result of rendering the "index.json" template with a randomly generated number between 1 and 6 assigned to the key :roll
.
The view (or template)
Reloading the page at this point will show a different error. Now the controller is defined, but Phoenix expects every controller to have a corresponding view, so we'll have to make one. Call it /lib/firehose_web/views/role_view.ex
and add the minimum code to make the module a Phoenix view:
defmodule FirehoseWeb.RollView do
use FirehoseWeb, :view
end
At this point, we're close but the render function in the controller had "index.json" as its first argument and we don't have anything in our app to handle that yet. We could create a template named index.json.eex
and put it in the templates
directory (under firehose_web) and the page would load. However, with JSON responses, the standard path is to write render functions in the view directly instead of writing a template. Just return a map and Phoenix will automatically convert it into a JSON response!
We'll add two function heads to the new view—one for valid input and one for everything else:
defmodule FirehoseWeb.RollView do
use FirehoseWeb, :view
def render("index.json", %{roll: num}) when is_integer(num) do
%{status: "ok", roll: num}
end
def render("index.json", _), do: %{status: "error"}
end
Done!
Go to localhost:4000/api/roll and you should see something like {"status": "ok", "roll": 4}
. Hit refresh and see more random results between 1 and 6.
Part 2: Phoenix JSON API: Rendering many
No Comments