Building a web application using Cowboy
In this section, we will take a look at some of the individual components of the Cowboy web server and use them to build a simple web application in Elixir.
Creating a new Mix project
Let’s start by creating a new Mix
project by entering the following in your terminal:
$ mix new cowboy_example --sup
What is Mix?
Mix is a build tool written in Elixir. Its purpose is to bundle all the dependencies required by a project and provide an interface to run tasks that rely on the application environment. If you’re familiar with the Ruby world, you can think of Mix as a combination of Rake and Bundler.
Passing the --sup
option to the mix new
command allows us to create a Mix project with a supervision tree. A supervision tree (or a supervisor) is a process that simply monitors other processes and is responsible for automatically restarting any process within the tree if it fails or crashes. We will be using the supervision tree in this application to start and supervise our web server process to make sure it is started when the application is started and to ensure that it keeps running.
Now, we will add Cowboy
as a dependency to our project by adding it to the mix.exs
file’s dependencies:
mix.exs
defmodule CowboyExample.MixProject do # ... defp deps do [ {:cowboy, "~> 2.8"} ] end end
Follow it up by running mix deps.get
from the project’s root directory, which should fetch Cowboy as a dependency.
Adding a handler and router to Cowboy
Now that we have added Cowboy, it is time to configure it to listen on a port. We will be using two functions to accomplish that:
:cowboy_router.compile/1
: This function is responsible for defining a set of requested hosts, paths, and parameters to a dedicated request handler. This function also generates a set of rules, known as dispatch rules, to use those handlers.:cowboy.start_clear/3
: This function is responsible for starting a listener process on a TCP channel. It takes a listener name (an atom), transport options such as the TCP port, and protocol options such as the dispatch rules generated using the:
cowboy_router.compile/1
function.
Now, let us use these functions to write an HTTP server. We can start by creating a new module to house our new HTTP server:
lib/cowboy_example/server.ex
defmodule CowboyExample.Server do @moduledoc """ This module defines a cowboy HTTP server and starts it on a port """ @doc """ This function starts a Cowboy server on the given port. Routes for the server are defined in CowboyExample.Router """ def start(port) do routes = CowboyExample.Router.routes() dispatch_rules = :cowboy_router.compile(routes) {:ok, _pid} = :cowboy.start_clear( :listener, [{:port, port}], %{env: %{dispatch: dispatch_rules}} ) end end
The preceding function starts a Cowboy server that listens to HTTP requests at the given port. By pattern matching on {:ok, _pid}
, we’re making sure that :cowboy.start_clear/3
doesn’t fail silently.
We can try to start our web server by calling the CowboyExample.Server.start/1
function with a port. But, as you can see, we will also need to define the CowboyExample.Router
router module. This module’s responsibility is to define routes that can be used to generate dispatch rules for our HTTP server. This can be done by storing all the routes, parameters, and handler tuples in the router module and passing them to the :
cowboy_router.compile/1
call.
Let’s define the router module with the route for the root URL of the host (/
):
lib/cowboy_example/router.ex
defmodule CowboyExample.Router do @moduledoc """ This module defines all the routes, params and handlers. This module is also the handler module for the root route. """ require Logger @doc """ Returns the list of routes configured by this web server """ def routes do [ # For now, this module itself will handle root # requests {:_, [{"/", __MODULE__, []}]} ] end end
We will also be using CowboyExample.Router
itself as the handler for that route, which means we have to define the init/2
function, which takes the request and its initial state.
So, let us define the init/2
function:
lib/cowboy_example/router.ex
defmodule CowboyExample.Router do # .. @doc """ This function handles the root route, logs the requests and responds with Hello World as the body """ def init(req0, state) do Logger.info("Received request: #{inspect req0}") req1 = :cowboy_req.reply( 200, %{"content-type" => "text/html"}, "Hello World", req0 ) {:ok, req1, state} end end
As you can see in the preceding code, we have defined the routes/0
function, which returns the dispatch rules routes for our web application. For the handler module, we’re currently using the CowboyExample.Router
module itself by defining the init/2
function, which responds with "Hello World"
and a status of 200
, whenever invoked. We have also added a call to the Logger
module to log all requests to the handler. This will increase visibility while running it in the development environment.
In order for our web server to start up when the app is started, we need to add it to our application’s supervision tree.