January 19, 2015

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 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.

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