Hello World web app in Elixir, part 2 - Plug

This is a continuation of Hello World web app in Elixir, part 1 - Cowboy. I am going to reference that post a few times because there are many similarities between the two apps, so you should probably read that first.

Plug

Plug is a little bit like Ruby’s Rack. It provides adapters for different web servers, similar to Rack handlers. It also provides a specification for writing composable modules, similar to Rack middleware. A nice introduction to Plug can be found here.

Mixfile

It’s very similar to the first one. I added Plug to both deps and applications. I am also using a stable older version of Cowboy, as advised in Plug’s docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# mix.exs
defmodule HelloWorld.Mixfile do
  use Mix.Project

  def project do
    [app: :hello_world,
     version: "0.1.0",
     elixir: "~> 1.3",
     deps: deps()]
  end

  def application do
    [
      mod: {HelloWorld, []},
      applications: applications(Mix.env)
    ]
  end

  defp applications(:dev), do: applications(:all) ++ [:remix]
  defp applications(_), do: [:cowboy, :plug]

  defp deps do
    [
      {:cowboy, "~> 1.0.0"},
      {:plug, "~> 1.0"},
      {:remix, "~> 0.0.1", only: :dev}
    ]
  end
end

Config

Again, almost identical as the first one except for a different port number (8001 -> 8002), to be able to run both apps at the same time.

1
2
3
# config/config.exs
use Mix.Config
config :hello_world, port: 8002

HelloWorld

1
2
3
4
5
6
7
8
9
10
# lib/hello_world.ex
defmodule HelloWorld do
  use Application

  def start(_type, _args) do
    port = Application.get_env(:hello_world, :port)
    Plug.Adapters.Cowboy.http(HelloWorld.Router, [],
      [port: port])
  end
end

I am starting the Cowboy’s HTTP server via the Plug’s adapter.

1
 Plug.Adapters.Cowboy.http(HelloWorld.Router, [], [port: port]) 

The first argument, HelloWorld.Router, is a module that adheres to the Plug’s specification. The second argument, [], is a list of options that will be passed to the module’s init function. The third argument is a list of options for the Cowboy’s HTTP server.

I will not write a plug module from scratch myself. There already are a lot of plugs ready to be used. I will use the Plug.Router to match paths to responses.

HelloWorld.Router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# lib/hello_world/router.ex
defmodule HelloWorld.Router do
  use Plug.Router

  plug Plug.Logger
  plug :match
  plug :dispatch

  get "/hello" do
    hello(conn)
  end

  get "/hello/:name" do
    hello(conn, name)
  end

  match _ do
    goodbye(conn)
  end

  defp hello(conn, name \\ "World") do
    body = "Hello, #{String.capitalize(name)}!"

    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, body)
  end

  defp goodbye(conn) do
    body = "Goodbye!"

    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(404, body)
  end
end

The router is a plug itself, but also has its own plug pipeline. This one line takes care of logging:

1
plug Plug.Logger

Those two plugs are necessary to be able to use the Plug’s router DSL:

1
2
plug :match
plug :dispatch

I’m using the get macro to match GET requests:

1
2
3
4
5
6
7
get "/hello" do
  hello(conn)
end

get "/hello/:name" do
  hello(conn, name)
end

As far as I know there is no way to define an optional segment in the path, so I need to match the two paths separately and use a hello function with an argument with a default value to respond to the requests with plain text:

1
2
3
4
5
6
7
defp hello(conn, name \\ "World") do
  body = "Hello, #{String.capitalize(name)}!"

  conn
  |> put_resp_content_type("text/plain")
  |> send_resp(200, body)
ene

Here I’m using the match macro to match all the other paths and methods:

1
2
3
match _ do
  goodbye(conn)
end

Running the app

I am starting the app with mix run --no-halt and trying it out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl -w "\n%{http_code}\n" http://localhost:8002/hello
Hello, World!
200

$ curl -w "\n%{http_code}\n" http://localhost:8002/hello/john
Hello, John!
200

$ curl -X POST -w "\n%{http_code}\n" http://localhost:8002/hello
Goodbye!
404

$ curl -w "\n%{http_code}\n" http://localhost:8002/hi
Goodbye!
404