#22: Static Site with Elixir

Elixir 1.4.5

Plug 1.4.3

Cowboy 1.1.2

View source on GitHub


In this episode let’s build a simple website that serves up static content. While we could use Phoenix, it would be a bit much for what we need.

Instead let’s use Plug.Router to build it.

To start let’s go to the command line and create a new Elixir project with:

$ mix new teacher

Then we’ll open our new project. Let’s open our ‘mixfile’ and we’ll need to add plug as a dependency. We’ll also add include Cowboy to use as our web server.

mix.exs

  defp deps do
    [{:plug, "~> 1.4"},
     {:cowboy, "~> 1.1"}]
  end

Now let’s go to the command line download our dependencies:

$ mix deps.get

With that let’s define the routes for our site. To do that we’ll need to create a router.

Let’s create a new directory in ‘lib’ named ‘web’ and then a we’ll create our ‘router.ex’ inside of it.

We’ll define our module and we’ll use Plug.Router.

And we’ll include the ‘match’ plug, which is responsible for matching our routes.

And the ‘dispatch’ plug, which receives/processes the matched route.

Looking at the router docs, we’ll see the router gives us a simple DSL - or domain specific language - that we can use to define the routes of our application.

Let’s create our first route at ‘/elixir’. And inside we’ll define what happens when someone hits the route.

Let’s use Plug’s send_resp to send a response with a status of 200 and a simple message for the body.

Let’s also define a ‘catch-all’ match that will catch all other requests to our application. And inside we’ll set our catch-all to send a status of 404 and a message.

lib/web/router.ex

defmodule Web.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/elixir" do
    send_resp(conn, 200, "I love <3 Elixir")
  end

  match _ do
    send_resp(conn, 404, "This is not the page you're looking for.")
  end

end

Now that we have a basic router defined - we need to create an application module, which we’ll use to supervise our router.

Let’s open our ‘teacher’ module and remove the boilerplate that was created with it.

Since this will be our application module, we’ll first invoke use Application and then define the start callback.

We won’t use the start type or any arguments for this demo - so let’s ignore them. And inside our start callback, let’s call our supervisor.

We’ll use Supervisor.start_link/2 which takes a list of children to supervise as the first argument.

And the second argument will be a keyword list of any options we want to specify.

Let’s use the one_for_one restart strategy, which restarts only the child process that’s terminated.

Now we need to define our ‘children’.

Since we’re using ‘cowboy’ for our web server let’s check out the [documentation] for Plug’s cowboy adapter.

We see there’s a child_spec function that starts a Cowboy server. It even gives us a great example on how to use it.

We’ll go ahead and grab this example. And back in our module let’s modify it. Let’s keep the same scheme - http- and port - 4001 - but we’ll need to specify our Router module for our plug.

lib/teacher.ex

defmodule Teacher do
  use Application

  def start(_type, _args) do
    children = [
      Plug.Adapters.Cowboy.child_spec(:http, Web.Router, [], [port: 4001])
    ]
    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

Now that we have our application module defined, let’s go back to our ‘mixfile’.

In it we’ll need to update the application function to include our Teacher module. This is the module that will be invoked when our application is started.

mod needs a tuple, where the first element is the module we want started - in this case our teacher module..

And in the second element we can supply any arguments we want - in this case we’ll just use an empty list.

mix.exs

…  
  def application do
    [mod: {Teacher, []},
     extra_applications: [:logger]]
  end
…

With that let’s go to the command line and we’ll call

mix run --no-halt

This will keep our application running after the command.

Now let’s go to our browser and test our routes. We’ll first go to ‘localhost:4001/elixir’ - and great we see our message. Now let’s test out a route that we haven’t defined and great our catch-all is triggered: ‘localhost:4001/asdf’.

With our routes working let’s take this a little further and render templates.

Let’s first create an html template - ‘elixir.html’ - in lib/web.

Template path: lib/web/email.html

<h1>I <3 Elixir</h1>

Then let’s open our router and update our ‘/elixir’ route to render our new template.

We’ll call plug’s put_resp_header to set the content-type header.

Then we’ll pipe that into plug’s send_file function. Giving it the same status of 200 and then we’ll give it our new HTML file we’ve just created.


  get "/elixir" do
    conn
    |> put_resp_header("content-type", "text/html; charset=utf-8")
    |> send_file(200, "lib/web/elixir.html")
  end

Now let’ go to the command line and restart our application.

And if we go back to our ‘localhost:4001/elixir’ route - we see our new template is being rendered.