Building an Elixir Web App

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 pipelines which are a series of plugs (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.

Hold onto your butts

Update

See my follow up post on composing Ecto queries for more detailed information on that topic.