Building useful bots with Node.js and Botkit

Faye Hayes
Jan 9 · 10 min read

Process automation is essential to a growing team. Backstage Capital has grown quickly to over 30 remote teammates. We’ve celebrated a ton of wins recently, such as investing in 100 impressive startups and launching 4 city-based accelerators. Despite how amazing our team is (and honestly, we are) with producing initiatives, we aren’t immune to growing pains. It can be difficult to stay in-sync across the Crew.

As a distributed team, lots of our communication happens in Slack. Slack bots are typically micro-projects that make your life easier by automating workflow tasks and information sharing. One of our experimental projects is a stand-up bot. Rather than holding a time-intensive meeting, each teammate can open a DM with the bot, go through a series of questions (think: having a conversation with the bot), and the bot will post the answers to a shared channel that anyone can access.

Simple right?

From the office: Michael Scott looking as confused as I was when we started.

Well, if you’re anything like me, that initial setup can be confusing. I love using Slack, but, to me, the documentation is very circular. As they revamp their systems and docs, I found that a lot of the legacy functionality that I was looking for was phased out in favor of the all-encompassing Slack Apps.

What was missing for me when creating our Slack bot was documentation that explained the what, how, and the why. And that’s what I’ll be sharing with you below. We will create a basic Slack App that listens for keywords and phrases, responds to slash commands, and can post to specific channels using incoming webhooks. We’ll finish this off by deploying to Heroku.

This post assumes little to no familiarity with Node.js, Slack Apps, Heroku, or Botkit. Check the footnotes for resource links, including the full git repository with all the code from this tutorial.¹


Setting up your Slack App

Gone are the days when you could use Slack integrations à la carte. Instead, you must set up a Slack App and activate those features you would like to use. For this application, we’ll be setting up webhooks, slash commands, and a bot user.

  1. Navigate to api.slack.com and click “start building”.
  2. Enter information about your Slack App in the screen below.
Create Slack App webpage.

Congratulations! You’ve created a Slack App and should have been redirected to its homepage.


Setting up your local Node.js application

  • Create the directory you’ll be using for your bot. My commands looked like this:
    mkdir example-app && cd example-app && npm init && git init
  • Install your dependencies (including the versions that worked for me as reference).
    npm install botkit@^0.6.21 && dotenv@^6.2.0
  • Create an index.js file and let’s go!
    touch index.js

Great! Now you’ve got a Slack App online, and a bare-bones Node.js app locally. Next, we want to set up both applications to be able to talk to one another.

The setup

At the top of your index.js file add:

var Botkit = require('botkit');
require('dotenv').config();

For my Slack bot (and probably every Slack bot I create here on out), I use the Botkit tool. The documentation is great and setup is relatively painless. Dotenv is what I use to hide my API keys. Hide ya keys, folks.¹

Run: touch .env .gitignore

In your newly created .gitignore file, write .env to keep your secure data out of your repo.

Commit and your good to go with setting up the rest of your environment variables! To understand what’s happening here, visit the dotenv repository.²

Next, add the following to your index.js file.

if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET || !process.env.PORT || !process.env.VERIFICATION_TOKEN) {
console.log('Error: Specify CLIENT_ID, CLIENT_SECRET, VERIFICATION_TOKEN and PORT in environment');
process.exit(1);
} else {
console.log('Good job, you have the variables!')
}

This if statement is checking to ensure you have all of the necessary variables BEFORE running the application. Let’s get the following variables from our Slack App.

  • CLIENT_ID
  • CLIENT_SECRET
  • VERIFICATION_TOKEN
  • CLIENT_SIGNING_SECRET
  • BOT_TOKEN

We retrieve those variables from the our Slack App. From the main page, you will find the following information.

Your Slack App homepage. Most of the keys you’ll need to start your app are here.

But what about the bot token?! Yes, we’ll get there…

But while we’re in the app, let’s get some of the config out of the way. From the left-side menu, navigate to Bot User and let’s add one. Once you’ve saved that user head on over to OAuth & Permissions.

From that screen you’ll see scopes. This sets the permissions you’d like your Slack App to have. Update to include the following.

Update scopes to include ‘bot’ and ‘commands’.

Next, install this app to your Slack workspace! At the top of OAuth & Permissions you’ll see the option to do so.

Green ‘install app to workspace’ button.

Next, in that same spot, the Bot User OAuth Access Token will appear. This is the BOT_TOKEN we’ll need in our app.

Now that we’ve gotten our tokens, let’s move back into our node application’s .env file. Add the following, swapping out the XXXX’s with the respective token.

CLIENT_ID=XXXX
CLIENT_SECRET=XXXX
VERIFICATION_TOKEN=XXXX
PORT=8765
BOT_TOKEN=XXXX
CLIENT_SIGNING_SECRET=XXXX

Great! Now we’ve gotten our environment variables out of the way!

Run node index.js in your terminal to ensure nothing’s broken.


Configuring the bot

Copy the following into your index.js file.

var controller = Botkit.slackbot({
json_file_store: ‘./db_slackbutton_slash_command/’,
debug: true,
clientSigningSecret: process.env.CLIENT_SIGNING_SECRET,
})

This is our controller configuration. Here’s each line explained:

  • The json_file_store helps save conversations within the app.
  • Setting debug to true gives you detailed console output to assist with debugging.
  • The clientSigningSecret is an additional security measure to ensure it’s your app talking to Slack.

Next, add the following.

controller.configureSlackApp({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
clientSigningSecret: process.env.CLIENT_SIGNING_SECRET,
scopes: [‘commands’, ‘bot’],
})

This bit of code is how we connect our node application to our Slack App online.

Now, the actual bot. Add the following to set it up.

var bot = controller.spawn({
token: process.env.BOT_TOKEN,
incoming_webhook: {
url: 'WE_WILL_GET_TO_THIS'
}
}).startRTM();

We’ve got to setup our web server. This is what we use to keep our application running. Normally, you’ll see express used in conjunction with node applications, but our controller config above came with a built in, set-up web server function that works just fine.

controller.setupWebserver(process.env.PORT, function(err, webserver){
controller.createWebhookEndpoints(controller.webserver);
controller.createOauthEndpoints(controller.webserver,
function(err, req, res) {
if (err) {
res.status(500).send(‘ERROR: ‘ + err);
} else {
res.send(‘Success!’);
}
});
});

That’s it for setting up the Slack bot. Next we’ll focus on some of the key things you can do with it.


Slack bot functionality

Listening for phrases

Add the following block of code to your index.js file.

controller.hears(‘hi’, ‘direct_message’, function(bot, message) {
bot.reply(message,”Hello.”);
});

There are a ton of different ways you can listen for conversations. See the Botkit documentation for the myriad of ways you can listen to and respond in a direct conversation. Fun fact: this is my favorite block of code. It’s my first indicator that my bot is working as expected. 🤖

Let’s test this in Slack. Log into the workspace that you installed the app on. Towards the bottom of the left-hand column you’ll see Apps. If you don’t already see your App there, click the little plus sign and find yours. Find it? Great! Open a DM with it and say, “hi.” You’ll know it’s working when the bot replies.

Slash commands

Slash commands are probably one of the coolest features you can implement with Slack. The possibilities are endless as to what you can achieve with them. Let’s get into it.

In your code, add the following.

controller.on(‘slash_command’, function(bot, message) {
// LOGIC GOES HERE
}

This is a catch-all command. It will listen for ALL slash commands in your application. However, if you were to go DM your app right now, this would not work. We’ll get into it later, but first you’ll have to add the slash command to your app as well as acceptable URLs that your Slack App recognizes.

The full slash command listener will end up looking something like this:

controller.on(‘slash_command’, function(bot, message) {
bot.replyAcknowledge()
switch (message.command) {
case “/echo”:
bot.reply(message, ‘heard ya!’)
break;
default:
bot.reply(message, ‘Did not recognize that command, sorry!’)
}
}

This was a HUGE gotcha for me. Slack slash commands require a response within 3000 milliseconds or else the user will see a timeout error. bot.replyAcknowledge() solves for this.

The switch statement listens for the various slash commands that you can set up in your application.

Before we can test these commands, we’ve got to set up some acceptable URLs that Slack will recognize. Localhost is okay for testing listeners, but for slash commands we’ll need an HTTPS URL.

We’ll start by generating HTTP ports using ngrok.

Using ngrok

This generates a URL that will look something like this: http://xxxxxx.ngrok.io

Now that we’ve got a temporary URL, let’s set them up in our Slack App.

Authenticating your new URL

  1. From the main page, navigate to OAuth & Permissions. Here you’ll find ‘Redirect URLs’. Add your newly created URL like so: http://xxxxxx.ngrok.io/oauth
  2. In another tab, navigate to http://xxxxxx.ngrok.io/login

You should redirected to a blank page that says, “Success!” If not, time to debug. Double-checking your environment variables would be a great place to start.

Create a new slash command

  1. From the main Slack App page, navigate to Slash Commands.
  2. Click ‘Create New Command’.
  3. Under command, write: /echo (this is what we named our command earlier)
  4. Under Request URL, write: http://xxxxxx.ngrok.io/slack/receive
  5. Save.

Let’s test it! Back in your Slack workspace. Go back into your DM with your app and write /echo. If your bot responds, you’ve set it up correctly.

Interactive webhooks

Interactive webhooks are needed to post to specific channels from your application. A possible scenario is if you don’t want the bot to respond directly to you, rather having the response posted to a shared channel across your team.

  1. From your Slack App main page, navigate to incoming webhooks and turn it on.
  2. Click ‘Add New Webhook to Workspace’ to generate a webhook for a channel in your workspace — any channel will do!
  3. Choose a channel it can post to and ‘Authorize’.
  4. Copy that URL and add it to the your bot definition like this:
var bot = controller.spawn({
token: process.env.BOT_TOKEN,
incoming_webhook: {
url: 'https://hooks.slack.com/services/xxxxxx/xxxxxxxx/xxxxxxxx'
}
}).startRTM();

Great, we’ve set up an incoming webhook! Let’s see it in action.

Start by adding a new listener.

controller.hears('webhook', 'direct_message', function(bot, message) {
bot.sendWebhook({
text: "Hey we've got the webhook!"
},function(err,res) {
if (err) {
console.log('web err', err)
}
});
});

Test it by typing webhook in the your app’s DM channel. Did the webhook post to the appropriate channel? Yes? Good job!

What if you want to dynamically update the webhook URL based on the user’s response? You would have to update your bot’s configuration. We’ll do a simple one using an if statement.

  1. Start by generating a new webhook to a different channel.
  2. Update webhook listener to listen to multiple words by swapping ‘webhook’ out for [‘webhook’, ‘webhook2’].
  3. Add an if statement to the method block of code.
if (message.text == 'webhook') {
bot.config.incoming_webhook.url = [YOUR_NEW_WEBHOOK]
} else {
bot.config.incoming_webhook.url = [YOUR_OLD_WEBHOOK]
}

The full listener will look something like this:

controller.hears(['webhook', 'webhook2'], 'direct_message', function(bot, message) {
if (message.text == 'webhook') {
bot.config.incoming_webhook.url = 'https://hooks.slack.com/services/YOUR/NEW/WEBHOOK'
} else {
bot.config.incoming_webhook.url = 'https://hooks.slack.com/services/YOUR/OLD/WEBHOOK'
}
bot.sendWebhook({
text: "Hey we've got the webhook!"
},function(err,res) {
if (err) {
console.log('web err', err)
}
});
});

Test it out by DM’ing your app webhook & webhook 2. Does it post to multiple channels based on the text? Great. You’ve set it up correctly.


Deployment

It’s time to deploy the application so it’s live and ready to use! We’ll be deploying our application to Heroku.

  1. Specify your node version in the package.json file.
"engines": {
"node": "8.x"
},

2. Add a start script.

"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},

3. Run heroku create from your command line inside the root of the project. Heroku will give you a temporary URL for your project. Save it. Make sure you have the Heroku CLI installed, then run heroku login to authenticate.³

4. Add your environment variables to Heroku. In the Heroku online dashboard, find your new project. There should be a tab across the top that says, ‘Settings’.

Heroku dashboard tabs.

Under the settings you’ll see ‘reveal config vars’ — add all of the variables from your .env file.

5. Update all of the URLs in your Slack App dashboard. This means updating the Redirect URL AND the slash command URL prefixes to be the Heroku-generated URL.

6. Authenticate. Navigate to YOUR_HEROKUAPP_URL/login to authenticate your app with Slack.

Test out your commands in Slack. If it works, you’re good to go.

A note on deployment: when your Heroku app is using a free plan, your Heroku application will fall asleep after 30 minutes of inactivity. As a result, you’ll start to get a 500 error. To fix that, you’ll need to upgrade to a paid plan or manually re-authenticate by navigating toYOUR_HEROKUAPP_URL/login.


That’s it! The makings of a bare bones Slack bot. A lot of the functionality that we’ve introduced were once stand-alone integrations that you’d add into your Slack workspace one-by-one. With the Slack App, you can bundle all the features into one application. This saves time and makes it super easy to find all the information you need to get your bot up and running!

I hope this tutorial was helpful for you! If you have any burning questions, feel free to comment or message me directly.

Happy bot hacking!


About the Author

Hi all, I’m Faye! Finally making my official entrance into Green Room. I met Arlan on Twitter and I joined the Backstage Capital Crew shortly after in January 2018 as a software engineer. I get to work on tools and projects that primarily benefit our team and Headliners (portfolio companies). In addition to working with Backstage, I co-founded a startup called PlayLoops, a GIF engine that enables progressive organizers to run efficient digital campaigns.

Resources

  1. Tutorial source code: https://github.com/fhayes301/slackbot-example-app
  2. Dotenv: https://github.com/motdotla/dotenv
  3. Botkit: https://github.com/howdyai/botkit
  4. Heroku CLI: https://devcenter.heroku.com/articles/heroku-cli

Green Room

Welcome to the Green Room blog. Go behind the scenes at Backstage Capital, where we're betting big on underrepresented tech startup founders.

Faye Hayes

Written by

Freelance #Dev (fayemyrettehayes.com). Making GIFs inclusive with @PlayLoops. Engineer @ Backstage Capital. Alum UC Irvine, Dev Bootcamp. Tweet: @fayemhayes

Green Room

Welcome to the Green Room blog. Go behind the scenes at Backstage Capital, where we're betting big on underrepresented tech startup founders.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade