An Erlang, A Cowboy, and A Mustache Walk Into A Bar

2025-12-18

Having just arrived at the ACT airport at 10:30AM for my flight at 6:30PM, I decided to try out another programming language. This time it's Erlang.

Given my current fascination with htmx, I though it a good idea to see if I could get a basic cowboy web server working with some HTML templating engine. We won't look at using htmx here but as templating is important for it, it seemed interesting enough to motivate me to learn Erlang

Soranoba's mustache library called bbmustache seemed the most approachable.

Code for this project can be found here.

Getting Started

The most difficult thing about getting started with Erlang wasn't the language, or the libraries, but rather the build system.

I'm sure Rebar3 is a great build system for those in the know, but for an outsider it's pretty opaque and has a steep learning curve.

The easiest and most well documented template to get started with is the release template which we can instantiate with rebar3 new release example_server.

Adding dependencies

The release template allows for multiple applications to be defined in the same project. Because of this we have to add our dependencies in two places; rebar.config and apps/example_server/src/example_server.app.src.

In rebar.config we'll change the following line.

{deps, []}.

We'll add our two dependencies, cowboy, and bbmustache.

{deps, [bbmustache, cowboy]}.

Now usually, that'd be all it takes to add the two dependencies. Annoyingly at the moment though it seems like rebar3 has some issues with versioning that makes it think cowlib (a dependency of cowboy) has an invalid version.

You can see this error now if you run rebar3 compile, but we'll fix the issue right away by adding some overrides. Don't ask me how this works yet though, I'm still new. I found this info in the issues for the cowboy project.

This should be added at the root of rebar.config. I put mine right below deps.

{overrides, [
  {override, cowboy, [{deps, [cowlib, ranch]}]}
]}.

Now you should be able to run rebar3 compile without seeing any errors.

We're not quite done though. This adds the two dependencies to the release so rebar3 pull them in and compiles them, but we also need to add them to our app so rebar knows we depend on them.

Here we open apps/example_server/src/example_server.app.src and add bbmustache and cowboy to our app's dependencies.

{application, example_server, [
    {description, "An OTP application"},
    {vsn, "0.1.0"},
    {registered, []},
    {mod, {example_server_app, []}},
    {applications, [
        kernel,
        stdlib, % Don't forget this comma.
        bbmustache, % Here,
        cowboy % and here
    ]},
    {env, []},
    {modules, []},
    {licenses, ["Apache-2.0"]},
    {links, []}
 ]}.

rebar3 compile should still return without errors if we've done everything correctly.

Now Where The Heck Does My Code Go?

Now we've got our project set up and building with our dependencies, where do we actually put our logic? By the looks of it code in Erlang applications is usually monitored by supervising code so it can be automatically restarted.

To do this we need to make a Child Spec that describes how to start the cowboy web server and when to automatically restart it. We'll supply the cowboy web server with Dispatch Rules that describe how to run our custom handlers.

First, open up apps/example_server/src/example_server_sup.erl. This module launches the supervisor. In particular, we want to modify the init function, adding our child specification.

init([]) ->
    %& Create the routes and map them to dispatch
    %% rules. Note that this should be moved to
    %% its own module once it gets more complex.
    Dispatch = cowboy_router:compile([
        {~"localhost", [{~"/", handler_index, []}]}
    ]),
    %% Create a child spec for the cowboy server
    %% so it can be restarted if it fails.
    %% Note that this could also be returned from a
    %% function in another module.
    CowboyChildSpec = #{
        %% This `cowboy_web_server` is an `atom()` that
        %% just acts as a uniqe identifier for the
        %% process as far as I can tell
        id => cowboy_web_server,
        start =>
            {cowboy, start_clear, [
                cowboy_web_server,
                [{port, 8000}],
                #{
                    env => #{dispatch => Dispatch}
                }
            ]},
        restart => permanent,
        shutdown => 5000
    },
    SupFlags = #{
        strategy => one_for_all,
        intensity => 0,
        period => 1
    },
    %% Add the CowboyChildSpec to the ChildSpecs
    %% for our supervisor
    ChildSpecs = [CowboyChildSpec],
    {ok, {SupFlags, ChildSpecs}}.

Let's check out Dispatch first.

Dispatch = cowboy_router:compile([
    {~"localhost", [{~"/", handler_index, []}]}
]),

cowboy_router:compile turns a mapping of hosts to lists of routes and their handling modules into actionable dispatch rules for the cowboy server.

Here handler_index is a module with -behaviour(cowboy_handler) that we haven't implemented yet. This module defines how requests to the root (/) of the server are handled. Note ~ here just means the string is encoded as UTF-8.

Next, we'll create the handler.

A Toy Handler

So we can test to make sure we haven't done anything wrong yet, we'll create a placeholder handler for the index. We'll fill this in with templating logic making use of bbmustache next, but for now we'll create a new file at apps/example_server/src/handler/handler_index.erl.

-module(handler_index).
-behaviour(cowboy_handler).
-export([init/2]).

init(Req, State) ->
    Req_1 = cowboy_req:reply(
        200,
        #{~"content-type" => ~"text/html"},
        "<h1>Hello, World!</1>",
        Req
    ),
    {ok, Req_1, State}.

Note the line -behaviour(cowboy_handler). This enforces that this module contains the required functions to be considered a cowboy_handler. If you're using the language server from the Erlang Language Platform, you can actually generate the required functions as a code action in your editor once -behaviour has been added.

In our init function here we create a reply to the request with the code 200 (ok), a content-type of text/html with a body that contains a simple heading.

If you run rebar3 shell from the root of our release to start the server, then check out http://localhost:8000 in your browser, you should see the HTML that we're serving.

Templates Are Easy, Rebar3 Is Weird

Now, we'll write a quick mustache template for our index page called index.html but where do we put it? We need to find some way to ship files with our release when we build it. This is what the /priv directory is for.

Where apps/example_server/src contains the Erlang source files, apps/example_server/priv contains any files that are required at runtime and should be shipped along with the application. Erlang provides the code:priv_dir function specifically to find where this /priv directory is, as it varies depending on how your code is running (release, debug, etc).

What we'll do then is create a new module at apps/example_server/src/template.erl to help us find, read, and compile templates from our /priv directory.

-module(template).
-export([load/1]).

-doc "Reads and parses a mustache file from /priv".
load(TemplateName) ->
    %% Get the /priv dir of the example_server app
    PrivDir = code:priv_dir(example_server),
    %% Ensure the results is filename() by checking that it's
    %% a string() which is a list of chars.
    case is_list(PrivDir) of
        true ->
            %% If it is, generate the file path
            TemplatePath = filename:join([PrivDir, TemplateName]),
            %% Then read template from the path
            {ok, TemplateBinary} = file:read_file(TemplatePath),
            %% And compile it
            bbmustache:parse_binary(TemplateBinary);
        false ->
            error({priv_dir_not_found, example_server})
    end.

We'll use this to load an index.html template and generate a new index from that template next.

A Marginally More Interesting Index

First, we'll need a template in apps/example_server/priv/index.html.

<h1>{{heading}}</h1>
{{#paragraphs}}
  <p>{{.}}</p>
{{/paragraphs}}

I know, truly a work of art right? In reality you'd hope to have some more complex things to display for all this work to be worth doing.

Now this is in here, we can rewrite our handler_index.erl module.

-module(handler_index).
-behaviour(cowboy_handler).
-export([init/2]).

init(Req, State) ->
    % Get the template for the index
    Template = template:load("index.html"),
    % Create a map for the template
    Data = #{
        "heading" => "Hello, World!",
        "paragraphs" => ["Paragraph 1", "Paragraph 1"]
    },
    % Compile the template
    Body = bbmustache:compile(Template, Data),
    Req_1 = cowboy_req:reply(
        200,
        #{~"content-type" => ~"text/html"},
        Body,
        Req
    ),
    {ok, Req_1, State}.

Here we get the template from our /priv directory using the template module we wrote, define some data in a map to fill the template, then compile the template into the final HTML to send along in the body.

Running rebar shell, we should now see our template-generated HTML at http://localhost:8000. If you don't, good luck.

You should be able to build a shippable project with rebar3 release, but we might leave that as an exercise to the reader because I've got to go catch a plane.