Slack custom slash command - check domain availability with Express.js, Node.js and Heroku

Bots are awesome, I’ve always been in favour of letting computers do the work so that we don’t have to. A well-programmed bot can save hours of time and effort. One of the best platforms for bots right now is Slack. The out-of-the-box integrations are awesome, but sometimes you need something custom. For this they offer custom slash commands, webhooks, and bots. Today I’ll be describing the steps I took to create my first custom slash command in Slack, using Node.js, Heroku, an API for looking up domain names, and of course, Slack. 🙂

My first port of call was to consult someone who’s done this already. A github repository from David McCreath has an excellent step-by-step tutorial on how he went about creating a custom slash command for checking if a site is live (even if I did submit a pull request to fix a broken link, hope you don’t mind David!). He uses PHP and cURL. Since I’m more of a Javascript baby, I decided to try it with Node.js, running on Heroku.

What is a custom slash command in Slack?

A custom slash command is just a keyword written after a slash, such as “/makemecoffee” or “/getweather Dublin”, where “Dublin” in this case is the city in which the user wants to check the weather in. Programmers will feel quite at home with such a familiar terminal-like command structure, but I’m curious as to how the general public and less techy employees might feel about it, but that’s a discussion for another day. Slack sees this command, takes the text that comes after this command, and sends that information to a URL somewhere on the web.

On the other end of that URL sits an application that can take in the commands passed to it in the Slack conversation, and perform any necessary tasks before returning a response or performing an action, like making a cup of coffee or consulting a weather API to get the weather in Dublin. In my case, I want my app to check the availability of a domain name that I specify.

Node.js and Express.js

First off, I had to get a basic application up and running locally in Node. I downloaded the latest update from, and followed this easy tutorial in installing Express.js and getting a basic “Hello World” application up and running. After running the command “node app.js”, I could view the result at localhost:3000. Awesome.


I thought now was a good time to add Git. So I initialised my directory as a Git repo using “git init”, and set up a GitHub repository for storing it remotely. Check it out, fork it and let me know what you think.


It had been a while since I had played around with Heroku, so I had to babystep my way back through getting it set up. Luckily, Heroku has some awesome documentation, and by following this tutorial, I was up and running in no time. I ran the app and deployed to heroku, but alas…


It’s never easy. But after much wrangling, log-checking and Googling, I discovered that my package.json was missing a node engine, and my app.js file was listening on port 3000 explicitly, whereas Heroku assigns a port dynamically on dyno startup. By adding a code snippet to get the app.js to listen to process.env.PORT || 3000, my issue was fixed and I finally had my Hello World app running. Now for the fun stuff.

Configuring the custom slash command in Slack

Following David’s tutorial I mentioned earlier, I navigated to, and began the setup. I inputted my heroku URL that I now know to work, and added hints that will pop up when the user types the command. Slack is big on user experience, so providing immediate feedback is crucial to minimising confusion and keeping the user informed and happy.

Listening for POST requests

Our slash command will send a POST request to the Heroku application, so we need to configure our app.js file to catch it, and do something with the “payload” which will contain the actual domain that we want to look up. Without listening for this POST request, the command will return an error in Slack. This called for a look at the Exrpess.js docs, where I found the code i needed (below)


My app is now listening for post requests, and simply returning “post request to homepage”


You can see my first request got returned with the message “Cannot POST /”. That’s because I hadn’t configured the app to respond to POST requests. The next request however, returned the text “POST request to homepage”, which is exactly what it’s supposed to do… for now 🙂

PS: Did you notice you can add your own image or Emoji to display along with the response??? I’m starting to get giddy now! 😀

Looking up the domain

For the purposes of this project, I found a free API I could use to query the availability of different domain names. It’s from Blaazetech, I found them on Mashape, and you can check it out the specific API here. It’s slightly limited in the responses it can give, but it covers some of the main TLD’s (.com, .net, .org, .biz, .mobi, .co), although, who uses .biz or .mobi any more??

The API accepts a GET request, within which will be the domain name that the user wants to check the availability of. So within my app.js file, where I’m responding to the slash command’s POST request, I’ll need to send a GET request, and then when I receive that response, I’ll send that back to Slack with a custom message. So, how do we send GET requests with Node/Express? Why, we use a library of course! 🙂

But first

Lets take a look at what our request from Slack actually looks like. I know from the Slack docs that it’s the “text” property that I want to extract from the body of the request. For the moment I just want to log this to the console within Heroku so that I know I’m on the right track before I build out the rest of the application. We need to use the bodyParser module for node to parse the POST request. Through some error logs and Googling I discovered that bodyParser is no longer bundled with Express v4, so we need to install it ourselves via npm. Take a look here on how to achieve that. There’s also a StackOverflow question that may help with any problems.


The code


The Heroku logs

By telling the app to use bodyParse, and accessing the ‘text’ property on the body of the request, I’m successfuly logging out the test domain “blerg” when I type “/checkdomain blerg” into Slack. Sweet, now I can feed that into the API using Unirest 🙂

Using the Unirest library

I found the Unirest library because it’s used right there in the example for the API I’ll be using, so why NOT use it? The query will have to be concatenated onto the end of the URL as a query parameter, and we already know how to get that, so we’re sorted. Like before, I’m going to take it slow and just log the response for the moment, before trying to do anything with it.

So, what does my code look like right now? You can see where I’m just logging the result.status, result.headers and result.body to the console.


And the result in the Heroku logs?


Success! It seems that isn’t available, darn it! But is. 🙂

Handling the API’s response


Ok cool, so we can now take the text from a custom slash command within Slack, and use an API to check if it’s available across different top level domains. But so far all we’re sending back to Slack is the text string “received POST request, check your logs”. It’s time to upgrade this to show the user some useful information.

Baby steps, return a string first

If you check out Slack’s documentation on slash commands, you’ll see that there are some interesting things that we can do regarding formatting. We’ll get to that in just a minute. For now, let’s just return the result.body of the API’s response in a basic string format. All we need to do is convert that JSON object to a string, for which we shall use JSON.stringify().

So the code becomes


And the result is


Success! It works, it actually works! Technology is amazing when it works! 😀

Styling the Slack response

As I mentioned earlier, there are some very nice formatting rules we can apply to our responses to custom slash commands that enrich the response with contextual colours and other pretty things. We’re going to need to use message attachments and the fields associated with them. We won’t use them all, however. We can also use a handy little tool Slack provides called message builder that shows us what our messages will look like in real time. Awesome.

The fields we’ll need

Each of the domain lookups (.com, .net, .org, .biz, .mobi, .co) will be its own message attachment, and in each one we’ll need certain fields.

  • Fallback – This is necessary plain text for devices that can’t show formatted text such as mobile devices or IRC chat. We’ll just construct a basic string based on whether that domain name is available or not for each TLD, and will actually be the same as our Title text below.
  • Color – Oooh nice, we can use green for the available TLD’s, and red for the taken ones 🙂
  • Title – This will be our main text, for example: “ is not available :(“

And that’s it for now. What does that look like? the handy message builder can show us. I mocked up the JSON in the interactive message builder.


Looks good! let’s get that into our code. First off, let’s create the object that we’ll send back to Slack. For the moment, it will just contain a text property with a success message, and an attachments property that will hold an array of attachments, one for each TLD in the API’s response. The attachments property will be an empty array initially, and we’ll populate it when we get the API’s response. I also created green and red variables here to hold the colours


Then, when we receive a response from the Unirest request to the domain search API, we want to loop over the result.body object and create an attachment for each one, pushing it to the attachments array when it’s built.


And the result in our Slack conversation?


Awesome! I’d call that a success 🙂

Some loose ends

Ok, so the command actually works. But now is where I start to consider sanitising inputs. What happens when I have pesky users that don’t use the command correctly and enter multiple words or special characters. Well, I’m in luck, as the API I’m using actually handles those errors for me quite elegantly, removing spaces and special characters from the query automatically, and returning a response regardless! Sweet!


I’m not too happy with those smileys though, we can do better there. A quick look at the emoji cheat sheet, and I can modify my code to show proper emojis, using :white_check_mark: for the approving tick, and :x: for the disapproving X 🙂



That’s better. But ya know what, I’m still not feelin it’s 100%. I think I want the available domains listed first, and the unavailable ones listed last, so that they’re grouped. I’ll need to add an if statement that will push() the attachment to the attachment array if the domain is unavailable, and unshift() if the domain is available. This should group the domains nicely.


The updated code with unshift() or push()


Domains are now grouped together based on availability


What now

Overall I’m really happy with how this little project turned out, and I hope I wrote it in a way that was helpful and easy to understand. I had a lot of help from libraries and API’s of course, Express, Unirest, Blaazetech domain search API etc. Also, the docs for Node.js, Heroku and of course Slack were extremely helpful. In the future, I could build this custom slash command out into a full-blown Slack app, the docs for which are here.

In the meantime, all my code is available on GitHub here. Fork it, submit pull requests to make it better, or use it for yourself. 🙂

Want to hire me?

I love a challenge, and BOTs are definitely the future. If you have an idea for a Slack BOT or a Facebook chat BOT, get in touch and let’s make it a reality. 🙂

You've successfully subscribed to Chris Dermody
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.