Building A Slack Bot With Elixir Part 1

Having started out as a Rails shop, Bendyworks is always on the lookout for new tools that give use the same combination of power and developer friendliness. We've been watching Elixir grow in popularity in the last few years, and are attracted to its marriage of functional programming and concurrency capabilities with a friendly Ruby-ish syntax.

We've been experimenting with it on personal projects for a while now, and have enjoyed the experience enough that we've begun to choose it over Ruby/Rails for developing some of our internal tools.

In this series of posts, I'll cover one of these internal applications; a basic Slack bot. In this case it'll only be responsible for fetching weather forecasts, but as many developers are doubtless familiar, Slack's API is becoming something of a de facto tool for building automated services that a team can access.

This first post will cover the basics of setting up and using Cowboy and Plug, two important tools in the Elixir/Erlang web services arsenal. The second part will cover implementing a Slack interface to handle the common task of scraping a URL, parsing the result, and sending a message back to Slack.

Setup

To follow along with this tutorial, you'll first need to install Elixir. Once that's finished, you can use mix, Elixir's integrated build tool, to create a new Elixir project called "weatherbot" by running

mix new weatherbot

Dependencies

Elixir uses hex as its package manager, which is tightly integrated into the boilerplate project generated by mix new. Adding new dependencies is as easy as modifying the mix.exs config file and running mix deps.get.

To begin with, we'll just be using some simple web server libraries.

Cowboy

Cowboy describes itself as a "Small, fast, modern HTTP server for Erlang/OTP". It aims to be fully compliant with the modern web, and supports standard RESTful endpoints as well as Websockets, HTTP/2, streaming, and a laundry list of other features.

It is also used by the Phoenix Elixir framework as its base HTTP server.

Plug

Plug is a middleware library similar in some ways to Rack, only even more modular and easy to use.

While Cowboy was originally developed for Erlang, Plug is pure Elixir. Unsurprisingly, it is also a core part of the Phoenix framework. However, it also provides its own router implementation that will suffice for a simple server of the sort we're building.

Installing Plug and Cowboy

Plug and Cowboy can be installed be modifying the deps function of the mix.exs configuration to be:

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

And running mix deps.get

Hello HTTP Server

To get started, lets just build a simple Plug routes file and an accompanying test. First create a new folder lib/weatherbot, and create a file in it called router.ex with the following code:

defmodule Weatherbot.Router do
  use Plug.Router
  use Plug.Debugger, otp_app: :weatherbot

  plug Plug.Logger
  plug Plug.Parsers, parsers: [:json, :urlencoded]
  plug :match
  plug :dispatch

  post "/webhook" do
    send_resp(conn, 200, "ohai")
  end

  match _ do
    send_resp(conn, 404, "not_found")
  end
end

Note that Plug.Logger and Plug.Debugger were not strictly necessary to include, but they'll help us see what's going on in our application and diagnose anything that goes wrong.

You'll also have to modify the main project file (lib/weatherbot.ex) to run Cowboy using the Plug router we've defined.

defmodule Weatherbot do
  use Application

  def start(_type, _args) do
    children = [ Plug.Adapters.Cowboy.child_spec(:http, Weatherbot.Router, [], port: 4000) ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

The use Application call and use of a Supervisor are all part of OTP, which, if you're not familiar, is somewhere between a standard library and a general framework for Erlang and Elixir. It's far too large a topic to cover in this post, but as a short gloss for what's going on in weatherbot.ex; we're defining our project to be an OTP "Application", and using a concurrency management abstraction called a "Supervisor" to start and watch over the Cowboy server process.

Finally, we'll need to tell Elixir to use the Weatherbot module as the basis of our application. Change the application function in mix.exs to the following:

def application do
  [
    extra_applications: [:logger],
    mod: {Weatherbot, []}
  ]
end

To try it out, run the app with mix run --no-halt, and use your HTTP client of choice to make a POST request to localhost:4000/webhook . You should get "ohai" as a response.

Testing

Mix comes bundled with ExUnit, a unit testing framework for Elixir. It's syntax, structure, and output should all feel pretty familiar if you're used to Rspec, unittest, jasmine, and other popular unit testing frameworks.

We can quickly write an ExUnit test for the code we have by leveraging the Plug.Test module.

Create the file test/weatherbot_test.exs with the following content:

defmodule WeatherbotTest do
  use ExUnit.Case
  use Plug.Test

  doctest Weatherbot

  alias Weatherbot.Router

  @opts Router.init([])

  test "responds to greeting" do
    conn = conn(:post, "/webhook", "")
           |> Router.call(@opts)

    assert conn.state == :sent
    assert conn.status == 200
    assert conn.resp_body == "ohai"
  end
end

Which can be run with mix test.

Next Steps

This part of the tutorial covered the basics of setting up and defining routes for an Elixir web server. We now have a good base configuration, which we'll use in Part 2 to build a more complex service that can receive, process, and respond to input from Slack.

Thanks to @geeksam for noticing and pointing out a few mistakes in an earlier version of this post.


Category: Development