Being new to Elixir and Phoenix, I spend quite some time in the projects' documentation. One thing that stood out for me recently is the first sentence of Phoenix’s Plug documentation:
Plug lives at the heart of Phoenix’s HTTP layer and Phoenix puts Plug front and center.
So naturally, I felt compelled to take a deeper dive into Plug and understand it better. I hope the following article will help you out in understanding Plug.
As the readme puts it, Plug is:
- A specification for composable modules between web applications
- Connection adapters for different web servers in the Erlang VM
But, what does this mean? Well, it basically states that Plug 1) defines the way you build web apps in Elixir and 2) it provides you with tools to write apps that are understood by web servers.
Let’s take a dive and see what that means.
Web servers, yeehaw!
One of the most popular HTTP servers for Erlang is Cowboy. It is a small, fast and modern HTTP server for Erlang/OTP. If you were to write any web application in Elixir it will run on Cowboy, because the Elixir core team has built a Plug adapter for Cowboy, conveniently named plug_cowboy.
This means that if you include this package in your package, you will get the Elixir interface to talk to the Cowboy web server (and vice-versa). It means that you can send and receive requests and other stuff that web servers can do.
So why is this important?
Well, to understand Plug we need to understand how it works. Basically, using
the adapter (
plug_cowboy), Plug can accept the connection request that comes
in Cowboy and turn it into a meaningful struct, also known as
This means that Plug uses
plug_cowboy to understand Cowboy’s nitty-gritty
details. By doing this Plug allows us to easily build handler functions and
modules that can receive, handle and respond to requests.
Of course, the idea behind Plug is not to work only with Cowboy. If you look at this SO answer from José Valim (Elixir’s BDFL) he clearly states “Plug is meant to be a generic adapter for different web servers. Currently we support just Cowboy but there is work to support others.”
Okay, now that we’ve scratched the surface of Cowboy and it’s Plug adapter, let’s look at Plug itself.
If you look at Plug’s README, you will notice that there are two flavours of plugs, a function or a module.
The most minimal plug can be a function, it just takes a
(that we will explore more later) and some options. The function will
manipulate the struct and return it at the end. Here’s the example from the
If you look at the function, it’s quite simple. It receives the connection
struct, puts its content type to
text/plain and returns a response with an
HTTP 200 status and
"Hello world" as the body.
The second flavour is the module Plug. This means that instead of just having a function that will be invoked as part of the request lifecycle, you can define a module that takes a connection and initialized options and returns the connection:
Code blatantly copied from Plug’s docs.
Having this in mind, let’s take a step further and see how we can use Plug in a tiny application.
Plugging a plug as an endpoint
So far, the most important things we covered was what’s Plug and what is it used for on a high level. We also took a look at two different types of plugs.
Now, let’s see how we can mount a Plug on a Cowboy server and essentially use it as an endpoint:
What this module will do is, when mounted on a Cowboy server, will set the
Content-Type header to
text/plain and will return an HTTP 200 with a body of
Let’s fire up IEx and test this ourselves:
This starts the Cowboy server as a BEAM process, listening on port 3000. If we
cURL it we’ll see the response body and it’s headers:
You see, the
Content-Type of the response is set to
text/plain and the body
Hello world. In this example, the plug is essentially an endpoint by
itself, serving plain text to our
cURL command (or to a browser). As you
might be able to imagine at this point, you can plug in much more elaborate
Plugs to a Cowboy server and it will serve them just fine.
To shut down the endpoint all you need to do is:
What we are witnessing here is probably the tiniest web application one can write in Elixir. It’s an app that takes a request and returns a valid response over HTTP with a status and a body.
So, how does this actually work? How do we accept the request and build a response here?
Diving into the
To understand this, we need to zoom in the
call/2 function of our module
PlugTest. I will also throw in an
IO.inspect right at the end of the
function so we can inspect what this struct is:
If you start the Cowboy instance again via your IEx session and you hit
cURL (or a browser), you should see something like this
in your IEx session:
What are we actually looking at? Well, it’s actually the Plug representation of a connection. This is a direct interface to the underlying web server and the request that the Cowboy server has received.
Some of the attributes of the struct are pretty self-explanatory, like
request_path, etc. If you would like to go into detail what
each of these fields is, I suggest taking a look at
But, to understand better the
Plug.Conn struct, we need to understand the
connection lifecycle of each connection struct.
Just like any map in Elixir
Plug.Conn allows us to pattern match on it. Let’s
modify the little endpoint we created before and try to add some extra
IO.inspect function calls:
Plug.Conn allows pattern matching, we can get the
state of the
connection, print it out and return the connection itself so the pipeline in
call/2 function would continue working as expected.
Let’s mount this plug on a Cowboy instance and hit it with a simple
You see, when the connection enters the plug it’s state changes from
:set to finally
:sent. This means that once the plug is invoked the state of
the connection is
:unset. Then we do multiple actions, or in other words, we
invoke multiple functions on the
Plug.Conn which add more information to the
connection. Obviously, since all variables in Elixir are immutable, each of
these function returns a new
Plug.Conn instance, instead of mutating the
Once the body and the status of the connection are set, then the state changes
:set. Up until that moment, the state is fixed as
:unset. Once we send
the response back to the client the state is changed to
What we need to understand here is that whether we have one or more plugs in a
pipeline, they will all receive a
Plug.Conn, call functions on it, whether
to extract or add data to it and then the connection will be passed on to the
next plug. Eventually, in the pipeline, there will be a plug (in the form of an
endpoint or a Phoenix controller) that will set the body and the response status
and send the response back to the client.
There are a bit more details to this, but this is just enough to wrap our minds
Plug.Conn in general.
Now that we understand how
Plug.Conn works and how plugs can change the
connection by invoking functions defined in the
Plug.Conn module, let’s look
at a more advanced feature of plugs - turning a plug into a router.
In our first example, we saw the simplest of the Elixir web apps - a simple plug that takes the request and returns a simple response with a text body and an HTTP 200. But, what if we want to handle different routes or HTTP methods? What if we want to gracefully handle any request to an unknown route with an HTTP 404?
One nicety that
Plug comes with is a module called
Plug.Router, you can see
its documentation here.
The router module contains a DSL that allows us to define a routing algorithm
for incoming requests and writing handlers (powered by Plug) for the routes.
If you are coming from Ruby land, while
Plug is basically Rack, this DSL is
Let’s create a tiny router using
Plug.Router, add some plugs to its pipeline
and some endpoints.
Quick aside: What is a pipeline?
Although it has the same name as the pipeline operator (
|>), a pipeline in
Plug’s context is a list of plugs executed one after another. That’s really it.
The last plug in that pipeline is usually an endpoint that will set the body and
the status of the response and return the response to the client.
Now, back to our router:
Code blatantly copied from
The first thing that you will notice here is that all routers are modules as
Plug.Router module, we include some functions that make
our lives easier, like
If you notice at the top of the module we have two lines:
This is the router’s pipeline. All of the requests coming to the router will
pass through these two plugs:
dispatch. The first one does the
matching of the route that we define (e.g.
/hello), while the other one will
invoke the function defined for a particular route. This means that if we would
like to add other plugs, most of the time they will be invoked between the two
mandatory ones (
Let’s mount our router on a Cowboy server and see it’s behaviour:
When we hit
127.0.0.1:3000/hello, we will get the following:
As you can see, we received
world as the response body and an HTTP 200. But if
we hit any other URL, the router will match the other route:
As you can see, because the
/hello route didn’t match we defaulted to the
other route, also known as “catch all” route, which returned
oops as the
response body and an HTTP 404 status.
If you would like to learn more about
Plug.Router and its route matching
macros you can read more in
its documentation. We
still need to cover some more distance with Plug.
In the previous section, we mentioned the plugs
dispatch, and plug
pipelines. We also mentioned that we can plug in other plugs in the pipeline
so we can inspect or change the
Plug.Conn of each request.
What is very exciting here is that
Plug also comes with already built-in plugs.
That means that there’s a list of plugs that you can plug-in in any Plug-based
Let’s try to understand how a couple of them work and how we can plug them in
MyRouter router module.
This is a rather simple plug. It’s so simple, I will add all of its code here:
What this plug does is it turns any HTTP
HEAD request into a
That’s all. Its
call function receives a
Plug.Conn, matches only the ones
that have a
method: "HEAD" and returns a new
Plug.Conn with the
If you’ve been wondering what the
HEAD method is for, this is from
The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.
Let’s plug this plug in our
Plug.Router (pun totally intended):
cURL the routes we would get the following behaviour:
As you can see, although we didn’t explicitly match the
HEAD routes using the
head macro, the
Plug.Head plug remapped the
HEAD requests to
our handlers still kept on working as expected (the first one returned an HTTP
200, and the second one an HTTP 404).
This one is a bit more complicated so we cannot inline all of its code in this article. Basically, if we would plug this plug in our router, it will log all of the incoming requests and response statuses, like so:
This plug uses Elixir’s
under the hood, which supports four different logging levels:
:debug- for debug-related messages
:info- for information of any kind (default level)
:warn- for warnings
:error- for errors
If we would look at the source of its
call/2 function, we would notice two
logical units. The first one is:
This one will take Elixir’s
Logger and using the logging
level will log the
information to the backend (by default it’s
console). The information that is
logged is the method of the request (e.g.
POST, etc) and the request
/foo/bar). This results in the first line of the log:
The second logical unit is a bit more elaborate:
In short: this section records the time between the
start and the
(end) of the request and prints out the
difference between the two (or in
other words - the amount of time the response took). Also, it prints out the
HTTP status of the response.
To do this it uses
is a utility function that registers callbacks to be invoked before the
response is sent. This means that the function which will calculate the
and log it to the
Logger with the response status will be invoked by
Plug.Conn right before the response is sent to the client.
Wrapping up with Plug
You actually made it this far - I applaud you. I hope that this was a nice journey for you in Plug and it’s related modules/functions and that you learned something new.
We looked at quite a bit of details in and around
Plug. For some of the
modules that we spoke about we barely scratched the surface. For example,
Plug.Conn has quite a bit of more useful functions. Or
Plug.Router has more
functions in its DSL where you can write more elaborate and thoughtful APIs or
web apps. In line with this,
Plug also offers more built-in plugs. It even
has a plug which can serve static files with ease, and plugging it in your
Plug-based apps is a breeze.
But, aside from all the things that we skipped in this article, I hope that you understood how powerful the Plug model is and how much power it provides us with such simplicity and unobtrusiveness.
In future posts, we will look at even more details about other plugs in
but until then please shoot me a comment or a message if you’ve found this
article helpful (or not).