TL;DR: The Elixir language is dynamically typed with lots of helpful documentation. It builds on Erlang and the Erlang VM. You can be up and running almost immediately. If your app compiles, it's already deployable. The Phoenix framework for Elixir apps is genuinely exciting to use. Let's build an easy-to-use application that manages upcoming events, built with the Phoenix Framework and authenticated with Auth0. The final code can be found at the auth0-elixir-countdown GitHub repo.
Elixir language
Personally, I've been stuck under a rock as a developer. I've missed plenty of cool languages whilst playing with PHP—and the Elixir language is no exception. Fortunately for me, Elixir (like PHP) is a dynamically typed language with lots of helpful documentation. My brain didn't have to work too hard to build something practical and fun. Elixir builds on Erlang and leverages the Erlang VM. Its got a super small footprint and is surprisingly robust. You can be up and running almost immediately.
It doesn't stop there.
If your Elixir app compiles, it's practically deployable already and the how-to guides come ready to talk you through getting it online with Heroku.
“If your Elixir app compiles, it's practically deployable already!”
Tweet This
Note: This article was written assuming you have a good understanding of functional programming languages. Elixir is a functional, concurrent, general-purpose programming language. It runs on the Erlang virtual machine (BEAM). Elixir builds on top of Erlang and shares the same abstractions for building distributed, fault-tolerant applications. Check out Elixir School for lessons about the Elixir programming language.
Phoenix framework
You can't escape the Phoenix framework if you're looking for a good tool to build on in Elixir. They portray themselves as a fresh approach to tried and tested MVC development. Phoenix genuinely excites me. One aspect is just how well we can separate our concerns across an extremely well thought out, logical structure. Another is that it provides a fantastic command line interface.
“You can't escape Phoenix framework if you're looking for a good tool to build on in Elixir!”
Tweet This
Phoenix is an MVC framework written in Elixir and will be welcoming and comfortable to use for anyone familiar with Rails or Django.
The Phoenix framework provides the best of both worlds: high developer productivity and high application performance. It also has some interesting new twists like channels for implementing realtime features and pre-compiled templates for blazing speed.
What should we build?
With holiday season fast approaching and a growing family, we're counting down the days when we can sit at home, in front of our (fake) fireplace and open some presents! :)
So, I thought, why not build an application where I can see countdowns to my favorite events?
Getting ready
Install Elixir
Follow the Elixir language install guide here: http://elixir-lang.org/install.html
Next, we run a
mix
command to install the Hex package manager. Mix is a build tool that provides tasks for creating, compiling, and testing Elixir projects, managing its dependencies, and more. If you know Ruby well, Mix is Rake, Bundler, and RubyGems combined.Now use mix to install hex: https://hexdocs.pm/mix/1.0.5/Mix.Tasks.Local.Hex.html
$ mix local.hex
Install Phoenix
Phoenix needs at least Elixir 1.4 and Erlang 18 or later, run:
$ elixir -v Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Elixir 1.4.2
Now we can install Phoenix v1.3.0:
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new-1.3.0.ez
Other requirements
To follow this guide fully, make sure you also have PostgreSQL and npm installed.
If you choose to use another database provider, make sure you edit the configuration appropriately.
Our Phoenix application skeleton
Now we can create a new application with the Phoenix framework. Let's call our application "countdown".
$ mix phx.new countdown
It will now generate a skeleton application with config, controllers, routing, etc... Everything you need, really!
* creating countdown/config/config.exs * creating countdown/config/dev.exs * creating countdown/config/prod.exs ... * creating countdown/assets/static/favicon.ico
When prompted, fetch and install dependencies.
Phoenix uses Brunch.io for asset management. Brunch.io’s dependencies are installed via the node package manager, not mix. Phoenix will prompt us to install them at the end of the mix phx.new task. If we say “no” at that point, and if we don’t install those dependencies later with npm install, our application will raise errors when we try to start it, and our assets may not load properly. If we don’t want to use Brunch.io at all, we can simply pass --no-brunch to mix phx.new.
Fetch and install dependencies? [Yn] Y
It will fetch and compile our dependencies.
* running mix deps.get * running mix deps.compile * running cd assets && npm install && node node_modules/brunch/bin/brunch build
Once it's finished it will give you some helpful info to check that it's all working, running your application, and even how to interact with your app inside IEx (Interactive Elixir).
Check out the following to learn more about debugging techniques in Elixir using IEx.
So let's change directory and check we can run the application.
$ cd countdown && mix phx.server
It shows us the compilation including any warnings or errors including deprecations or unused variables. Very cool.
[info] Running CountdownWeb.Endpoint with Cowboy using http://localhost:4000 Compiling 13 files (.ex) .. ==> mime Compiling 1 file (.ex) warning: String.strip/1 is deprecated, use String.trim/1 lib/mime.ex:28 ... [info] Sent 200 in 18ms
Now we can take a look our app by visiting http://localhost:4000. It should look something like...
Countdown Application
CRUD pattern
We need to be able to store the events we are going to count down to.
Before we can start adding our data, we need to set up the database. Open up
config/dev.exs
, and edit the username and password near the bottom of the file. (They will both be set to “postgres” by default.)... config :countdown, Countdown.Repo, adapter: Ecto.Adapters.Postgres, username: "auth0", password: "phoenixelixir", database: "countdown", ...
If you didn't run it yet, let's create the database now.
$ mix ecto.create The database for Countdown.Repo has been created
Now comes the backbone of our application, the CRUD actions for the events. In order to achieve this, Phoenix supports us with four different generators:
Gen HTML creates: model, view, controllers, repository, templates, testsmix phoenix.gen.html
Gen Channel creates: channel and testsmix phoenix.gen.channel
Gen JSON, for APIs really, creates: model, view, controllers, repository, testsmix phoenix.gen.json
Gen Model creates: model and repositorymix phoenix.gen.model
We use the first generator which creates all resources and actions for us, the same as rails generators. We need to declare the name as a collection, singular and plural, and next the field names with types.
$ mix phx.gen.html Events Event events title due:datetime
We're pretty close to having a CRUD application ready to go!
* creating lib/countdown_web/controllers/event_controller.ex * creating lib/countdown_web/templates/event/edit.html.eex ... * injecting test/countdown/events/events_test.exs
Go and edit
lib/countdown_web/router.ex
and place resources "/events", EventController
inside the CountdownWeb
scope.... scope "/", CountdownWeb do pipe_through :browser # Use the default browser stack get "/", PageController, :index + resources "/events", EventController end ...
Now we can migrate our new model into the database.
$ mix ecto.migrate Compiling 10 files (.ex) Generated countdown app [info] == Running Countdown.Repo.Migrations.CreateEvents.change/0 forward [info] create table events [info] == Migrated in 0.0s
Let's run our server to make sure it's still working.
$ mix phx.server
If it is still working, this time we can head to http://localhost:4000/events and see our new CRUD interfaces.
Showing off our events
We could list all events on our homepage, but as we are going to have countdown clocks, it's important we only list future events. For this, we're going to need a way to fetch events in the future. Let's give it a go...
Edit
lib/countdown/events/events.ex
which is the repository module for our events.You'll find the query structure familiar, especially if you've used any well-known database abstraction layers, like Doctrine or Django ORM, or even if you are experienced with SQL itself. We're going to be selecting
from
Countdown.Events.Event
where due
is greater than or equal to the current UTC timestamp.query = from e in Countdown.Events.Event, where: e.due >= ^DateTime.utc_now
The docblock shows you how to run the method in IEx (interactive Elixir) and the format of the response, usually an example
.[%Map{}, ...]
... def list_events do Repo.all(Event) end + + @doc """ + Returns the list of future events. + + ## Examples + + iex> list_future_events() + [%Event{}, ...] + + """ + def list_future_events do + query = from e in Countdown.Events.Event, + where: e.due >= ^DateTime.utc_now + Repo.all(query) + end ...
Next, we add this to the controller and display some items on the homepage. Let's edit our homepage controller at
lib/countdown_web/controllers/page_controller.ex
... use CountdownWeb, :controller + alias Countdown.Events def index(conn, _params) do + events = Events.list_future_events() - render conn, "index.html" + render conn, "index.html", events: events end ...
Nowe we'll edit our homepage template at
lib/countdown_web/templates/page/index.html.eex
and add a very simple list of items, we'll come back and make turn this into our countdown clocks later.<div class="jumbotron"> <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2> <p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p> </div> + <ul> + <%= for event <- @events do %> + <li><%= event.title %>, <%= event.due %></li> + <% end %> + </ul> ...
Note: I've added last Christmas so we can see our query works, only showing future events.
Now we can see our list is limited to only events in the future. Perfect!
Adding our countdown clocks
We're going to use FlipClock.js to create our countdown clocks. Edit the base app template
lib/countdown_web/templates/layout/app.html.eex
... <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flipclock/0.7.8/flipclock.min.css" /> + <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/flipclock/0.7.8/flipclock.min.js"></script> ...
Now edit the homepage template
lib/countdown_web/templates/page/index.html.eex
and replace it with the contents below. It uses a very basic jQuery loop to apply FlipClock to each .countdownClock
element. The Bootstrap jumbotron is perfect for the quick and easy layout we want here because of its clear heading and very basic markup.<%= for event <- @events do %> <div class="jumbotron"> <span class="h1">Days until <%= event.title %></span> <p class="text-center"><span class="countdownClock" data-due="<%= event.due %>"></span></p> </div> <% end %> <script> $(document).ready(function() { $('.countdownClock').each(function(i, el) { var today = new Date(); var due = new Date($(el).data('due')); $(el).FlipClock((due/1000) - (today/1000), { clockFace: 'DailyCounter', countdown: true, showSeconds: false }); }); }); </script>
Our last little styling tweaks are just to center the countdown clocks and replace the logo with something more appropriate :)
Edit
assets/css/app.css
and add the following. You're going to need your own logo!/* This file is for your main application css. */ .auth0-logo { width: 200px; height: 80px; display: inline-block; background-image: url(http://placehold.jp/ffffff/003366/200x80.png?text=Auth0%20meets%0AElixir); background-repeat: no-repeat; background-size: 200px; } .jumbotron .flip-clock-wrapper { text-align: center; position: relative; width: auto; margin-top: 8em; display: inline-block; }
Now edit
lib/countdown_web/templates/layout/app.html.eex
to use the new logo styling.- <span class="logo"></span> + <span class="auth0-logo"></span>
We should see something like this, our own app in which we can control what events we display!
Authentication with Auth0
Last of all, we'll secure administration of events with Auth0's Centralized Login Page. Providing us with Auth0's JSON Web Token, we will be able to securely verify users inside the application.
“Auth0's JSON Web Tokens provide us the tools to securely verify and identify users inside our application!”
Tweet This
Sign Up for Auth0
You'll need an Auth0 account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 Client and API so Auth0 can interface with your app and API.
Set Up a Client App
- Go to your Auth0 Dashboard and click the "create a new client" button.
- Name your new app and select "Regular Web Applications".
- In the Settings for your new Auth0 client app, add
to the Allowed Callback URLs.http://0.0.0.0:4000/auth/auth0/callback
- Click the "Save Changes" button.
- If you'd like, you can set up some social connections. You can then enable them for your app in the Client options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter.
Note: On the OAuth tab of Advanced Settings (at the bottom of the Settings section) you can see the JsonWebToken Signature Algorithm is set to
. This is now the default, read more about RS256 vs HS256 here.RS256
Install the Ueberauth module
Edit
mix.exs
and add the Ueberauth
module to your dependencies and application compilation order.... def application do [ mod: {Countdown.Application, []}, - extra_applications: [:logger, :runtime_tools] + extra_applications: [:ueberauth, :ueberauth_auth0, :logger, :runtime_tools] ] end ... defp deps do [ ... - {:cowboy, "~> 1.0"} + {:cowboy, "~> 1.0"}, + {:ueberauth, "~> 0.4"}, + {:ueberauth_auth0, "~> 0.3"} ] end ...
You can also use a GitHub repository as your package source to use the latest changes. Beware—it might have breaking and unstable code. For example:
... defp deps do [ ... - {:cowboy, "~> 1.0"} + {:cowboy, "~> 1.0"}, + {:ueberauth_auth0, github: "sntran/ueberauth_auth0"} ] end ...
Now that it's configured we need to install our dependencies.
$ mix deps.get
Edit
config/config.exs
to configure Ueberauth
.... import_config "#{Mix.env}.exs" + +# Configures Ueberauth +config :ueberauth, Ueberauth, + providers: [ + auth0: { Ueberauth.Strategy.Auth0, [] }, + ] + +# Configures Ueberauth's Auth0 auth provider +config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, + domain: System.get_env("AUTH0_DOMAIN"), + client_id: System.get_env("AUTH0_CLIENT_ID"), + client_secret: System.get_env("AUTH0_CLIENT_SECRET")
Configured like this, we can now control our config through environment variables. For more information on how to define these check out this guide on bash environment variables.
Later, we'll be looking at starting our application with environment variables included. You can choose to set them up however you wish, but they're required at compile time to make our authentication work.
An example of how to launch the application with environment variables:
$ AUTH0_DOMAIN=<Your domain> \ AUTH0_CLIENT_ID=<Your client ID> \ AUTH0_CLIENT_SECRET=<Your client secret> \ mix phx.server
Setup authentication
First, we'll create a new model for reading user info. Open a new file at
lib/countdown_web/models/user_from_auth.ex
. This model, courtesy of Thomas Peitz, will give us easy access to details obtained back from authentication payload. It also supports multiple formats of profile information. For example, providers like GitHub and Facebook give a profile image url, slightly different.defmodule UserFromAuth do @moduledoc """ Retrieve the user information from an auth request """ require Logger require Poison alias Ueberauth.Auth def find_or_create(%Auth{provider: :identity} = auth) do case validate_pass(auth.credentials) do :ok -> {:ok, basic_info(auth)} {:error, reason} -> {:error, reason} end end def find_or_create(%Auth{} = auth) do {:ok, basic_info(auth)} end # github does it this way defp avatar_from_auth( %{info: %{urls: %{avatar_url: image}} }), do: image # facebook does it this way defp avatar_from_auth( %{info: %{image: image} }), do: image # default case if nothing matches defp avatar_from_auth( auth ) do Logger.warn auth.provider <> " needs to find an avatar URL!" Logger.debug(Poison.encode!(auth)) nil end defp basic_info(auth) do %{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth)} end defp name_from_auth(auth) do if auth.info.name do auth.info.name else name = [auth.info.first_name, auth.info.last_name] |> Enum.filter(&(&1 != nil and &1 != "")) cond do length(name) == 0 -> auth.info.nickname true -> Enum.join(name, " ") end end end defp validate_pass(%{other: %{password: ""}}) do {:error, "Password required"} end defp validate_pass(%{other: %{password: pw, password_confirmation: pw}}) do :ok end defp validate_pass(%{other: %{password: _}}) do {:error, "Passwords do not match"} end defp validate_pass(_), do: {:error, "Password Required"} end
Next, create a new controller. Call it
AuthController
and create it at lib/countdown_web/controllers/auth_controller.ex
. This controller has a few responsibilities:- Callback Handles the success or failure of our auth request. On success, stores the user in a session.
- Logout Drops the session.
- Helpers Providers a basepath for our
strategy helpers, meaningUeberauth
will now allow us to authenticate with the Auth0 provider we setup./auth/auth0
defmodule CountdownWeb.AuthController do use CountdownWeb, :controller alias CountdownWeb.Router.Helpers plug Ueberauth alias Ueberauth.Strategy.Helpers def logout(conn, _params) do conn |> put_flash(:info, "You have been logged out!") |> configure_session(drop: true) |> redirect(to: "/") end def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do conn |> put_flash(:error, "Failed to authenticate.") |> redirect(to: "/") end def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do case UserFromAuth.find_or_create(auth) do {:ok, user} -> conn |> put_flash(:info, "Successfully authenticated as " <> user.name <> ".") |> put_session(:current_user, user) |> redirect(to: "/") {:error, reason} -> conn |> put_flash(:error, reason) |> redirect(to: "/") end end end
Now we need to add endpoints for the controller to the router. Edit
lib/countdown_web/router.ex
to configure these routes.defmodule CountdownWeb.Router do use CountdownWeb, :router + require Ueberauth ... + scope "/auth", CountdownWeb do + pipe_through :browser + + get "/:provider", AuthController, :request + get "/:provider/callback", AuthController, :callback + post "/:provider/callback", AuthController, :callback + end + ... scope "/", CountdownWeb do pipe_through :browser # Use the default browser stack get "/", PageController, :index + get "/logout", AuthController, :logout ...
Secure our application
To secure our events controller, edit
lib/countdown_web/controllers/event_controller.ex
and add a Plug
which will be called by the controller prior to every endpoint. This will now check our current session for a verified user.... alias Countdown.Events.Event + + plug :secure + + defp secure(conn, _params) do + user = get_session(conn, :current_user) + case user do + nil -> + conn |> redirect(to: "/auth/auth0") |> halt + _ -> + conn + |> assign(:current_user, user) + end + end def index(conn, _params) do ...
Now our
/events
endpoints are secured, we just need a helpful menu to navigate the site.Nearly there!
Lets edit our
PageController
found at lib/countdown_web/controllers/page_controller.ex
to add our current_user
property, allowing us to access it elsewhere.def index(conn, _params) do events = Events.list_future_events() - render conn, "index.html", events: events + render conn, "index.html", events: events, current_user: get_session(conn, :current_user) end
The base app template needs to be edited
lib/countdown_web/templates/layout/app.html.eex
, it is going to check for our current user. If we don't have one, we'll show a link to login.... <ul class="nav nav-pills pull-right"> - <li><a href="http://www.phoenixframework.org/docs">Get Started</a></li> + <%= if @current_user do %> + <li><a href="/">Home</a></li> + <li><a href="/events">Events</a></li> + <li class="active"><a href="/logout">Logout</a></li> + <% else %> + <li><a href="/">Home</a></li> + <li><a href="/auth/auth0">Login</a></li> + <% end %> </ul> ...
Start our application, remembering our environment variables.
$ AUTH0_DOMAIN=<Your domain> \ AUTH0_CLIENT_ID=<Your client ID> \ AUTH0_CLIENT_SECRET=<Your client secret> \ mix phx.server
Log in, have a play and it should look something like this!
Credits
A big thanks to Thomas Peitz for his help and suggestions.
Conclusion
We've learned how to build a basic CRUD application in Elixir. It utilizes the Phoenix framework, allowing us to create applications in a straightforward way using the simple command line interface. We've also learned how to write some basic security using Auth0 inside Elixir.
“Write some basic security using Auth0 inside Elixir!”
Tweet This
To learn more about the Phoenix Framework, check out phoenixframework.org.
To learn more about Elixir, check out elixir-lang.org.
I went into this project with no prior knowledge of Elixir or Phoenix. The result is, I believe I can probably deliver a robust application that is production ready and deployable faster using Phoenix than I could in some other languages. It has been an amazing and eye-opening experience and I'm looking forward to getting the opportunity to pick up more languages and doing something cool with them.
The final code can be found at the auth0-elixir-countdown GitHub repo.