Fork me on GitHub

Elixir Todo App

How to develop a functional user interface for a Todo Web Application

with Elixir, ElixirScript, Phoenix, React and Flux

Elixir Todo App

If you love writing your backend services in Elixir, you might ask yourself "why can't i use Elixir for my frontend development as well?". The good news is: you can! Although ElixirScript is not a finished product and still has some limitations, Bryan Joseph has already achieved impressive results in building an Elixir to JavaScript compiler.

This tutorial shows you how to implement a (more or less) complete Todo Application with Elixir and ElixirScript and that ElixirScript might become an ideal functional language for frontend development, which can bring back the fun into browser development!

The complete source code of the tutorial application can be found in this GitHub repository.

This tutorial focuses mainly on the frontend part of the application. It shows how functional programming together with React and a Redux-style design can lead to an improved application architecture. This architecture also borrows from the language Elm, in that it doesn't use UI components, but simply calls functions to render the complete user interface into React's virtual DOM.

The frontend code can be found in the directory web/static/exjs and consists of the following modules:

main.ex Main.start() is called when the application is loaded into the browser
store.ex Implements a Redux-style Flux store with reducers, subscribers and middlewares
reducers.ex Contains the reducer functions that implement the business logic
views.ex UI rendering functions and a single render() subscriber function
middlewares.ex Middleware functions for server synchronization
channels.ex Real-Time-Communication through Phoenix Channels
todo.ex Todo model functions
react_ui.ex Elixir macros that implement a DOM DSL (see https://github.com/bryanjos/elixirscript_react)

A note on the usability of this application:

Elixir Todo App is an example tutorial application which you can use to manage your todos, but is is far from being feature complete. Currently there is no support for multiple users, and there is no authentication and no authorization implemented. It is not optimized for performance (which is only a problem if you have millions of todo items) and lacks a lot of the features, that you can find in other todo or task list management apps.

Architecture and Technologies

Here is a quick overview of the Elixir Todo App architecture and the technologies used for it's implementation:

Server
The server is built with Phoenix and provides an endpoint for the Web-Application as well as REST endpoints for the backend services.
Database
There is a single Todo model that is managed with Ecto and stored in a PostgreSQL database.
Backend business logic
Besides simple model validation, there is no server side business logic.
REST API
The backend services are provided through a REST API (/api/todos).
Channels (WebSockets)
Changes to the todo list from multiple browsers are synchronized through Phoenix channel messages.
Frontend Rendering
React is used to render the Web frontend. Whenever the application state changes, the complete UI is created as a React virtual DOM.
Frontend business logic
A custom Flux store implementation that uses a Redux-style reducer/subscriber pattern, is used to implement the frontend's business logic.

Install and run the Elixir Todo App

Elixir and Erlang are available as packages for most operating systems (and Linux distributions). With the latest instructions that are available on the Elixir web page at http://elixir-lang.org/install.html, you should be able to install Elixir and Erlang on your system in a few minutes. You also need a PostgreSQL database running on your workstation. And since the JavaScript packages are managed with NPM, you finally need to have the latest version of NPM (which comes with Node.js) installed on your system as well. Now clone the GitHub repository, retrieve the package dependencies, create the database and start the Elixir Todo App server:

$ git clone https://github.com/grappendorf/elixir_todo_app.git
$ cd elixir_todo_app
$ mix deps.get
$ npm install
$ mix ecto.reset
$ mix phoenix.server
...
[info] Running ElixirTodoApp.Endpoint with Cowboy using http://localhost:4000

Eventually you can open the Elixir Todo App in your browser by going to the url http://localhost:4000.

After playing around a little bit with the app, let us now examine the different parts of the system in more detail.

REST API Backend with Phoenix and Ecto

I won't talk too much about the backend. It uses Phoenix to handle the HTTP requests, implements a simple REST API for the todo items and stores the todo items in a PostgreSQL database with Ecto. There are already numerous tutorials out there, that explain how Phoenix and Ecto work, so please refer to them if you need to learn more about this.

Here are the available API functions:

Path Method Description
/api/todos GET Retrieve a list of all todo items
/api/todos/:id GET Get a single todo item by it's id
/api/todos POST Create a new todo item
/api/todos/:id PUT Update a todo item
/api/todos/:id DELETE Delete a todo item
/api/todos/:id/states POST Change the state of a todo item (todo or done)

As an example here is the implementation of the GET /api/todos function:

defmodule ElixirTodoApp.Todo do
  use ElixirTodoApp.Web, :model

  defenum Status, todo: 0, done: 1

  schema "todos" do
    field :text, :string
    field :status, Status, default: :todo
    timestamps
  end

  def latest_first todos do
    from todo in todos,
    order_by: [desc: todo.inserted_at]
  end
end
defmodule ElixirTodoApp.TodoController do
  use ElixirTodoApp.Web, :controller
  alias ElixirTodoApp.{Repo, Todo}

  def index conn, _params do
    todos = Todo |> Todo.latest_first |> Repo.all
    render conn, todos: todos
  end
end
defmodule ElixirTodoApp.TodoView do
  use ElixirTodoApp.Web, :view

  def render "index.json", %{todos: todos} do
    todos |> Enum.map(&todo_to_json/1)
  end

  defp todo_to_json todo do
    %{
      id: todo.id,
      text: todo.text,
      status: todo.status
    }
end

Handling client business logic with a Flux store and reducers/subscribers

For the Elixir Todo App i don't use React's Redux library, but have implemented my own version of the Redux architecture. The complete store implementation can be found in the Store module (web/static/exjs/store.ex).

The store provides the following functions:

Function Description
new(initial_state) Create a new store with an initial state.
state(store) Retrieve the current state of the store.
reduce(store, reducer) Add a reducer to the store. This is a pure function of type (State, Action) :: State that receives the current state of the store and the action that is dispatched and transforms the current state into a new state.
subscribe(store, subscriber) Add a subscriber to the store. This is a function of type (State, State) :: none() that receives the new and old state of the store and is called for its side effects.
middleware(store, middleware) Add a middleware to the store. This is a function of type (:pre|:post, State, State, Action) :: Action that receives the new and old state of the store, and the action that is dispatched. A middleware is called before and after the reducers have been applied. Middlewares have side effects and are called to modify or dispatch new actions.
dispatch(store, action) This is the main function that keeps the system running. When we dispatch an action, first the middlewares are executed (:pre), then the reducers are applied, then the middlewares are called again (:post) and then the subscribers are executed.

This store together with the reducers and the subscribers reduces (no pun intended) our application architecture to this simple diagram:

Redux Pattern

Actions are coming in either from user activities or some other external events, reducers transform the current application state depending on these actions into a new state and subscribers perform side effects that will for example reflect these state changes back into the user interface.

First of all when the application starts up, the store is initialized in the applications start() function and an initial action is dispatched to load the todos from the server:

defmodule Main do
  def start _, _ do
    create_store()
    |> Store.middleware(&Middlewares.api/4)
    |> Store.reduce(&Reducers.reduce/2)
    |> Store.subscribe(&Views.render/2)
    |> Store.start

    Store.dispatch {:load_todos}
  end
end

Let us now look in detail at how the {:load_todos} action is handled. There is another version of this action {:load_todos, todos}, that contains the todos to displays. But initially we don't have any todos, so we must call the REST API to fetch them from the server.

Calling backend services through middlewares

defmodule Middlewares do
  def api :pre, _, _, {:load_todos} do
    do_get "/api/todos", fn json ->
      todos = Enum.map json, &(Todo.new_from_json &1)
      Store.dispatch {:load_todos, todos}
    end
    {}
  end
end

The middleware function handles the action {:load_todos}. It calls window.fetch("/api/todos") in the do_get() function to retrieve the todos and returns the empty action {}. At this point no reducer will be applied and the store state doesn't change (but you could also return another action like {:show_loading_spinner} for displaying some kind of "loading" state in the UI).

When do_get() succeeds, the todo list is generated from the JSON response and a new action {:load_todos, todos} is dispatched.

As another more complicated example take a look at this middleware function that puts a modified todo onto the server:

defmodule Middlewares do
  def api :post, %{todos: todos}, %{todos: old_todos}, action = {:update_todo, id, _} do
    todo = Todo.find_by_id(todos, id)
    todo_json = %{"text" => todo.text, "status" => Atom.to_string(todo.status)}
    do_put "/api/todos/#{id}", todo_json, fn _ -> end, fn _ ->
      show_error_toast "Server error while updating the todo."
      Store.dispatch {:undo_todo, Todo.find_by_id(old_todos, id)}
    end
    action
  end
end

This middleware function is called after all reducers have been applied. So the old state contains the old version of the todo and the new state contains the modified one. In case of a server error, me must undo the state change. So in the error function we dispatch a new action {:undo_todo, old_todo} that replaces the modified todo with the original one.

Implementing the business logic with reducers

Everything a program does can be described as a collection of reducer functions. They define how the application state changes, when a specific action (or event) has occurred. (You can even describe each step of an algorithm in a function as a single reducer, Dave Thomas ("The Pragmatic Programmer" and "Programming Elixir: Functional |> Concurrent |> Pragmatic |> Fun") has done a nice presentation on this topic)

Elixir's pattern matching allows us to implement reducers in a clean and expressive way. The following reducer function handles the {:load_todos, todos} action and sets the todos in the application state:

defmodule Reducers do
  def reduce state, {:load_todos, todos} do
    %{state | todos: todos}
  end
end

Another reducer for example handles the deletion of a todo:

defmodule Reducers do
  def reduce state = %{todos: todos}, {:delete_todo, id} do
    %{state | todos: Todo.delete(todos, id)}
  end
end

If we strive for high cohesion and a loose coupling between the reducers, most of them will be simple one-liners like these.

Rendering the Web-UI with React

You need to have some knowledge of how React works to understand the frontend code of the Elixir Todo App. So please read the React Tutorial if you haven't already done so.

Whenever the application state changes, the Views.render(new_state, old_state) function is called with the current application state as a parameter. Views.render() then calls various other functions of the Views module to render the UI into a virtual DOM. At the end, ReactDOM.render() renders the virtual DOM into actual DOM. This process is highly optimized, because React first creates a diff between the old virtual DOM and the new one and only updates the parts of the real browser DOM which have actually changed.

defmodule Views do
  use ReactUI
  def render state, _ do
    state
    |> Views.page
    |> ReactDOM.render(:document.getElementById("app"))
  end

  def page state do
    ReactUI.div do
      page_title state
      todo_input state
      todo_list state
      help state
    end
  end

  # ...more functios below...
end

This is the function that renders a single todo item:

def todo_item todo, edit_todo do
  if edit_todo && edit_todo.id == todo.id do
    todo_item_editor todo, edit_todo
  else
    todo_item_text todo
  end
end

We check if the todo item is currently edited by the user. We then either call a function todo_item_editor() that renders the todo item as an editor or a function todo_item_text() that renders the todo with plain text.

In todo_item_editor() the HTML structure of our user interface is defined by a DSL provided through some Elixir macros. These macros directly translate into calls of the React.createElement() function, so in essence this is similar to using JSX in React. We can emit DOM elements, define their properties and specify event handlers as anonymous Elixir functions.

def todo_item_editor todo, edit_todo do
  tr key: todo.id do
    td className: "width-100" do
      input value: edit_todo.text, autoFocus: true, className: "form-control",
        onChange: fn event ->
          Store.dispatch {:edit_todo_text, event.target.value}
        end,
        onKeyUp: fn event, _ ->
          if event.keyCode == @key_enter && edit_todo.text != "", do:
            Store.dispatch {:update_todo, edit_todo.id, edit_todo.text}
          if event.keyCode == @key_escape, do: Store.dispatch {:cancel_edit_todo}
        end
      ReactUI.div className: "pull-right todo-actions" do
        i className: "fa btn fa-save btn-success",
          disabled: edit_todo.text == "",
          onClick: fn _, _ -> Store.dispatch {:update_todo, edit_todo.id, edit_todo.text} end
        i className: "fa btn fa-remove btn-info",
          onClick: fn _, _ -> Store.dispatch {:cancel_edit_todo} end
        i className: "fa btn #{done_button_class todo}",
          onClick: fn _, _ -> Store.dispatch {:toggle_todo, todo.id} end
        i className: "fa fa-trash btn btn-danger",
          onClick: fn _, _ ->
            confirm "Do you really want to delete the todo \"#{edit_todo.text}\"?",
              fn -> Store.dispatch {:delete_todo, todo.id} end,
              fn -> Store.dispatch {:cancel_edit_todo} end
          end
      end
    end
  end
end

In todo_item_text() we again use these DSL macros to define a simple span that contains the todo text.

def todo_item_text todo do
  tr key: todo.id do
    td className: "width-100",
      onClick: fn _, _ -> Store.dispatch {:edit_todo, todo.id} end do
      span className: todo_text_class(todo) do
        todo.text
      end
    end
  end
end

And finally here is the todo_list(state) function that creates a table that contains a row for each todo item. This is done by simply mapping the todo list with the function that creates a table row for a single todo item. Also the list is filtered if the user only wants to display the active todos.

def todo_list state do
  ReactUI.div do
    # ...some code for other ui elements ommitted...
    table className: "table table-bordered table-striped tutor-container" do
      tbody do
        state.todos
        |> Enum.filter(fn todo -> !state.hide_done || todo.status != :done end)
        |> Enum.map(fn todo -> todo_item todo, state.edit_todo end)
      end
      # ...some code for other ui elements ommitted...
    end
  end
end

Real-Time-Updates from the server

When the todo list data was modified through some external event (you can for example open another browser window with your todo list and change some todos there), we need to synchronize this information with all clients. This is done through messages that are sent over Phoenix channels. These messages simply transport actions that are dispatched on the client.

For example when a new todo is created through the REST API, the TodoController broadcasts a :todo_was_created message to the clients:

defmodule ElixirTodoApp.TodoController do
  use ElixirTodoApp.Web, :controller

  def create conn, _ do
    with {:ok, todo} <- create_model(Todo.changeset %Todo{}, conn.body_params) do
      ClientChannel.publish_todo_created todo, conn.body_params["temp_id"]
      conn |> put_status(:created) |> json(%{id: todo.id})
    else
      {:error_create, errors} ->
        conn |> put_status(:bad_request) |> render_model_errors(errors)
    end
  end
end

defmodule ElixirTodoApp.ClientChannel do
  alias ElixirTodoApp.Endpoint

  def publish_todo_created todo, temp_id do
    Endpoint.broadcast("client", "action", %{
      action: :todo_was_created, params: [temp_id, todo.id, todo.text, todo.status]})
  end
end

On the client side a WebSocket connection is established in app.js and listens to messages on the client channel. When a new message is received, it is converted into an action that can be dispatched by the store:

defmodule Channels do
  def dispatchMessage msg do
    action = [String.to_existing_atom(msg.action) | msg.params] |> List.to_tuple
    Store.dispatch action
  end
end

Some final considerations

And there you have it. Except for a few configuration and boiler plate files, that's the whole application. It has a clean and simple architecture with only a few architectural elements to remember. Yes, it is a small application that is missing a lot of features, so we need to gather more experience when we create bigger systems.

Were are the frontend tests?

I'm glad you ask. This is the next item on my todo list. First of all we could test the frontend with JavaScript testing frameworks. But since our frontend is written in Elixir, we could also test it with Elixir's test frameworks if only we could stub out the browser specific APIs. I will update this tutorial if i have done some experiments on this.

I want to use ElixirScript, is it ready for production?

Let me cite from the ElixirScript's FAQ:

You can use ElixirScript on your front ends and have it work and interoperate with JavaScript modules. The problem is since most of the standard library is incomplete.

What about performance?

In the render() function the complete user interface is rendered into a virtual DOM whenever any value in the store state changes. Elm for example uses "lazy functions" to optimize this. These functions memoize their results and only recreate the DOM whenever the function parameters differ. Since we are using Reacts virtual DOM and React already optimizes the handling of updates to the real DOM (by creating a diff between the virtual DOM), we may get away with it. Also even in a very big application, only parts of the user interface are visible at a specific point in time and so most of the rendering functions are not called at all. But sure, more experiments and maybe some performance optimizations are needed for production grade applications.