Over the past few months I’ve been building a small internal application at work. I’ve been using Elixir, Ecto and Phoenix and it’s been an absolute blast. I thought it would be useful to put together a “lessons learned” blog post about the techniques I’ve found helpful using these tools to build a database-backed web app.
This post is not intended as an introducion to any of the these tools. I assume some knowledge of Elixir, Ecto and Phoenix.
Note: while Elixir is post-1.0, Ecto and Phoenix are not. Things move fast and this post may quickly become out of date.
Pipelines are your friend
Phoenix has the concept of pipeline
s which are a series of plug
s (think
rack middleware) that will be executed in order. You
can send all requests for a given scope of URLs through a pipeline.
In my application I have a pipeline for the browser, a pipeline for
authentication and a pipeline for API requests. I can mix and match these
pipelines to accept browser requests with or without authentication and API
requests with or without authentication. My router.ex
looks something like
this:
defmodule MyApp.Router do
use Phoenix.Router
pipeline :api do
plug :accepts, ~w(json)
end
pipeline :auth do
plug MyApp.Plug.Authentication
end
pipeline :browser do
plug :accepts, ~w(html)
plug :fetch_session
plug :fetch_flash
plug MyApp.Plug.CSRF
end
# public routes via the browser
scope alias: MyApp do
pipe_through :browser
# ...
end
# private routes via the browser
scope alias: MyApp do
pipe_through [:browser, :auth]
# ...
end
# public routes via the api
scope "/api/v1", alias: MyApp do
pipe_through :api
# ...
end
# private routes via the api
scope "/api/v1", alias: MyApp do
pipe_through [:api, :auth]
# ...
end
Separate your API endpoints with a scope
This leads me to my next point. I chose to separate my API with a distinct scope. This gives me the convenience of using the same controllers and actions to expose my API that I’m using to serve browser requests, but also allows me the flexibility of only exposing a subset of those routes (or entirely new actions and routes).
Suppose I have a ‘Page’ resource in my application that is accessible via the
browser but only the index
action is accessible via the API. I could do the
following:
scope alias: MyApp do
pipe_through [:browser, :auth]
resources "/pages", PageController
end
scope "/api/v1", alias: MyApp do
pipe_through [:api, :auth]
resources "/pages", PageController, only: [:index]
end
The PageController.index
action can now serve both API and browser requests,
but if a user tries to access PageController.show
via the API it will refuse
to serve JSON.
Create custom JSON serializers
Creating custom JSON serializers in Phoenix is easy and powerful. First, you
override the appropriate render
function in your view. Let’s continue with the
previous Page
example and assume the render function is called with a
collection of page
objects.
defmodule MyApp.PageView do
def render("index.json", %{pages: pages}) do
pages
end
end
Note that the return value of the render
call is just the provided pages
.
This won’t quite work yet. Phoenix expects anything that is returned by a JSON
action to implement the Poison.Encoder
protocol. This is where we can put our
custom serialization logic.
defimpl Poison.Encoder, for: MyApp.Page do
def encode(page, _options) do
%{
title: page.title,
body: page.body
} |> Poison.Encoder.encode([])
end
end
Tada! We create a custom JSON “view” of our data and Phoenix / Poison are smart
enough to iterate over our collection of pages
and build the JSON. Yahoo!
Build queries in your models, execute them in your controllers
I’ve found it convenient to have your models responsible for building queries and your controllers responsible for executing those queries.
defmodule MyApp.Page do
use Ecto.Model
import Ecto.Query
schema "pages" do
field :title, :string
field :body, :string
field :published, :boolean
end
def published do
from p in MyApp.Page,
where: p.published == true
end
end
defmodule MyApp.PageController do
use Phoenix.Controller
plug :action
def index(conn, _params) do
pages = MyApp.Page.published |> MyApp.Repo.all
render conn, :index, pages: pages
end
end
The biggest benefit this style is composibility of queries. I’ve started writing all my queries to expect a prior query and I then chain them together in my controller.
defmodule MyApp.Page do
use Ecto.Model
import Ecto.Query
schema "pages" do
field :title, :string
field :body, :string
field :published, :boolean
field :pubished_at, :datetime
end
def published(query) do
from p in query,
where: p.published == true
end
def recent(query) do
from p in query,
where: p.published_at > ^MyApp.Helper.Date.yesterday
end
end
defmodule MyApp.PageController do
use Phoenix.Controller
alias MyApp.Page
plug :action
def index(conn, _params) do
pages = Page
|> Page.published
|> Page.recent
|> MyApp.Repo.all
render conn, :index, pages: pages
end
end
Create shared partials by adding a shared view
I wanted to be able to render common HTML snippets across many of my templates
(such as rendering form errors). To do so, I created a SharedView
and
explicitly rendered the shared templates.
defmodule MyApp.SharedView do
use MyApp.View
end
<%= render MyApp.SharedView, "_errors.html", errors: @error %>
Wrap Up
Each of these topics deserves a full post itself and there are many techniques I haven’t covered (testing, changesets, custom validations, etc). I’m in awe of the speed at which the Elixir, Ecto and Phoenix communites are moving. Expect great things from Chris, Eric, José and the rest in 2015. Hold on to your butts.
Update
See my follow up post on composing Ecto queries for more detailed information on that topic.