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.