Building Blocks of Elixir: Plug

Learn about and practice the use of Plug in Elixir.

Welcome to the wonderful world of Plug!

We’ll be exploring the ability of this powerful Elixir tool to build web applications. This general introduction to Plug will guide you through a beginner project and maybe even level up your skills with the force.

Let's get started with the basics: what is Plug? 

According to the source of all knowledge, the github page, Plug is:

  1. A specification for composable modules between web applications
  2. Connection adapters for different web servers in the Erlang VM.

Plug allows us to connect to requests. It's the way that we interact with HTTP requests and build the HTTP responses necessary for a web application. It also provides ways to interact with connection data to transform it and return data that is understood by different web servers on the Erlang virtual machine.

Putting it all together, here are a couple examples of how you can plug in with Elixir.

We are going to put you to the test. Here is your opportunity to move from Padawan to Jedi Knight. In order to receive this honor, you will need to build a plug worthy of approval from the Jedi council.

Let us begin!

First, you generate a project with mix new jedi_plug.

You want to get into the jedi_plug directory and edit the mix.exs file by adding the plug_cowboy dependency. This will allow you to use the Cowboy webserver and its plug bindings. Here is what the deps section of your mix file should look like.

defp deps do
    [
      {:plug_cowboy, "~> 2.0"}
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
    ]
 end

Now you run mix deps.get to install the dependency. We want to alert the world of your status in the Jedi order so we will be building a message. In your lib folder, find the jedi_plug.ex file and open it. Here is what mine looked like.

defmodule JediPlug do
  @moduledoc """
  Documentation for `JediPlug`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> JediPlug.hello()
      :world

  """
  def hello do
    :world
  end
end

First you want to import Plug.Conn. Then you want to configure your options because you’re fancy. Finally, you will add the response call which should return your announcement to the world. Try it out! If you struggle, here’s what it could look like.

defmodule JediPlug do
  @moduledoc """
  Documentation for `JediPlug`.
  """
  import Plug.Conn

  def init(options) do
    options
  end

  def call(conn, options) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "#{options[:message]} Fear the dark side!")
  end
end

To use this, run iex -S mix. Then, run the following command:

Plug.Adapters.Cowboy.http(JediPlug, %{:message => "I am the future of the Jedi Order."})

You should see a response similar to this: {:ok, #PID<0.245.0>}. If you go to your browser and type in http://localhost:4000/, you should see the following message printed on the page:

<pre><code>I am the future of the Jedi Order. Fear the dark side Brian!</code></pre>

You're almost there. We can reconfigure our options to make this a smoother process that receives a parameter. First, exit the shell with the following command clearly written by a Sith Lord: System.halt.

Rewrite your init/1 function to put the given value of “I am the future of the Jedi Order.” under the key  :message in the options map. Then, pattern match the request path to bind name to some provided value. Here’s what your jedi_plug.ex file might look like afterwards.

defmodule JediPlug do
  @moduledoc """
  Documentation for `JediPlug`.
  """
  import Plug.Conn

  def init(options) do
    Map.put(options, :message, "I am the future of the Jedi Order.")
  end

  def call(%Plug.Conn{request_path: "/" <> name} = conn, options) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "#{options[:message]} Fear the dark side #{name}!")
  end
end

Now enter your iex session again with iex -S mix and run the following command:

Plug.Adapters.Cowboy.http(JediPlug, %{})

You should now be able to go to your browser and type in http://localhost:4000/Brian. You may notice that the path now has the added name of someone you can’t stand (see here, Brian who stole my skittles in 7th grade).

You should see the following message printed on the page:

I am the future of the Jedi Order. Fear the dark side Brian!

You are now officially a Jedi Knight; welcome to the order courtesy of Elixir and Plug. Now that you are a certified universe protector, let’s break down the process we just went through.

Plug can be a function or a module. A module plug can look like the example below. Our JediPlug was a module plug. The module plug has two functions. The first one is an `init/1` function which serves to initialize our options. The second one is a `call/2` function which takes in the connection (Plug.Conn struct) in addition to our initialized options and returns a connection.

defmodule MyModulePlug do
	def init( [ ] ), do: false
	def call( conn, _opts), do: conn
end

We can use the functions that are defined for the Plug.conn module to do any manipulation of the data.

assign(conn, key, value) # allows us to assign a value to a key in the connection
inform(conn, status, headers \\ [ ]) # sends an informational response to the client

Plugs can also be function plugs. As a function, plug takes a Plug.Conn struct and some possible options as given arguments and sends back a Plug.Conn struct. In case you were wondering (because I was), a struct is an extension ”built on top of maps (the go-to key-value data structure in Elixir) that provide(s) compile-time checks and default values.”

def function_plug(conn, _opts) do
	conn
end

Let’s rewrite our jedi_plug.ex file using a function plug to print the same message to the browser. You can use the Plug.Builder module to access the plug/2 macro which can receive our function plug. Then, we can build our jedi_plug function to return the message to the browser. Here’s an example of what it can look like:

defmodule JediPlug do
  	use Plug.Builder

  	plug :jedi_plug, %{message: "I am the future of the Jedi Order."}

  	def jedi_plug(%Plug.Conn{request_path: "/" <> name} = conn, options) do
    	conn
    	|> put_resp_content_type("text/plain")
    	|> send_resp(200, "#{options[:message]} Fear the dark side #{name}!")
  	end
end

If you enter your iex session (iex -S mix) and run the same command you used previously (Plug.Adapters.Cowboy.http(JediPlug, %{})), you should be able to go to the browser and see the same message printed.

Module and function plugs are the building blocks upon which the whole Phoenix web framework is built upon. For further exploration, read this article and explore the links below. Plug’s middleware capabilities extend beyond simple text printing but with knowledge of its fundamentals, we can build more creative and complex applications. You can use Plug to build the following:

May the plug be with you as you continue to explore the wonders of Elixir!

Sign up for The Viget Newsletter

Nobody likes popups, so we waited until now to recommend our newsletter, a curated periodical featuring thoughts, opinions, and tools for building a better digital world. Read the current issue.