Slack DevKit is a light-weight developer kit to build Slack Apps in node.js super fast. No previous knowledge about building Slack Apps or Bots needed. Also includes automatic support for verifying requests from Slack and responding to validations.

This was made to build Slack Apps on Glitch and AWS Lambda even faster, but it works everywhere!

Highlights

  • OAuth support without 3rd-party database
  • Verifies request signatures and/or verification tokens
  • Support for short-lived tokens with automatic refresh
  • Automatic retrieval of workspace's authentication info
  • Auto-parsing of string-encoded JSON payloads
  • Authenticated HTTPS client for Slack's API
  • Writeable datastore associated to each workspace
  • Automatic Slack payload normalization
  • Auto validates Events API challenges
  • Supports Single Channel Installations
View Examples

Installation

Slack DevKit can be installed using npm.
npm i slack-devkit
Once installed, you would require the package in your code.
const Slack = require('slack-devkit')
Creating new instances of Slack DevKit return four objects that can be used as needed.
const { server, router, client, app, lambda } = new Slack(settings)

Settings

Single Workspace Apps
Apps that are only used in one workspace, also known as Internal Integrations, simply require a Slack Access Token and a way to verify the request originated from Slack.
new Slack({
  access_token: "xoxa-XXXXXXXXXXXXX",
  signing_secret: "XXXXXXXXXXXXXXXX"
})
Multi-Workspace Apps
Apps that have distribution enabled require OAuth settings to be installed.
new Slack({
  scope: 'commands,files:write,links:read,links:write',
  client_id: "1212313.1231231231231",
  client_secret: "12312312323123123",
  signing_secret: "sdfsadfsadfasdfas"
})
Here's a list of all possible settings that can be used to configure Slack DevKit.
PropertyDescription
scopeSlack OAuth scopes
client_idSlack OAuth client id
client_secretSlack OAuth client secret
redirect_uriSlack OAuth redirect uri
signing_secretSlack signing secret
verification_tokenSlack verification token
access_tokenAccess token to use for Internal Integrations
datastoreA file path to write the datastore to or pass in an object to use an alternate datastore
slack_rootThe root domain to make Slack requests to
All arguments are optional, but may be required based on the App type.

Server

Local Host
The Slack Server is a preconfigured Express.js instance with the Router already attached.
const { server } = new Slack(settings)
...// routing stuff

// optionally pass in the port number to listen on
// server will be listening on localhost:3000
server.start(3000)
LocalTunnel
The server is preconfigured to use LocalTunnel if you've installed the module (it's not preloaded).
const { server } = new Slack(settings)
...// routing stuff

// optionally pass in the subdomain to use (xxxx.localtunnel.me)
// server will be listening on slackdevkit.localtunnel.me
server.startLocal('slackdevkit')

Requests

The Slack Request is populated on all incoming POST requests and will be attached to the Express.js request object. All helper methods on Payload can also be called from the Request.
server.post('/', (req, res) => {
  req.slack // this is the Slack Request object
})
API
Make authenticated calls to the Slack API by calling the api method on requests.
req.slack.api('users.info', { user: 'U12345' }).then(r => {
  r.data // the api results
})
Replies
Reply to any message by calling the reply method and pass in the message to reply with.
req.slack.reply({
  text: 'This is a reply!',
  attachments: [{
    text: 'boom!'
  }]
})
Unfurls
Respond to link_shared events and unfurl them with unfurl.
req.slack.unfurl({
  title: 'Unfurled Link',
  image_url: 'https://home.com/image.png'
})
Uploads
Respond to events with a file by calling upload and pass in files.upload arguments.
req.slack.upload({
  filename: 'some_file.png', 
  file: fileBuffer 
})
A few more payload helper methods that are used on request:
PropertiesDescription
actionInteractive message's selected action
selectionThe message's selected option
textThe message text
bot_idThe bot id the payload was sent from (if applicable)
channel_idThe channel id the payload was sent from
team_idThe team id the payload was sent from
user_idThe user id that sent the payload
event_typesEvent types that triggered this payload
MethodsDescription
subcommand(name)The sub-command arguments for a command
isSubCommand(name)Check if the a slash command contains a sub-command
is(event_type)Check if the payload is a type of event
match(regex)Match the message text against a regex

Router

The Slack Router is an Express.js Router that can be used as middleware in existing Express.js apps.
const express = require('express'),
  Slack = require('slack-devkit'),
  app = express()

const { router } = new Slack({
  scope: SCOPE,
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  signing_secret: SIGNING_SECRET
})

app.use(router)
Alternatively, you can pass the router into selected routes
app.get('/install', router, (req, res) => {
  req.slack // populated
})

app.post('/events', router, (req, res) => {
  req.slack // populated
})

Lambda

Slack DevKit has built-in support for AWS Lambda with DynamoDB . When constructing Slack DevKit, pass-in the DynamoDB table name to use in the datastore property.
const { lambda } = new Slack({
  scope: SCOPE,
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  signing_secret: SIGNING_SECRET,
  datastore: 'workspaces' // DynamoDB Table Name
})

exports.handler = lambda((slack, context, callback) => {
  slack // the Slack Request object

  // respond to lambda normally
  return context.succeed(slack.data)
}
Serverless.js
Slack DevKit's Lambda handler is easier to deploy using Serverless.js coupled with the included Serverless.js template.

DataStore

Usage
All requests from Slack come with a data object that contains authentication information about the workspace and any additional information you would like to save. This object gets populated after OAuth has been successful.
server.post('/', (req, res) => {
  // The DataStore associated with the calling workspace
  req.slack.data 

  // The OAuth access information for the workspace is available here
  const { access_token, team_id } = req.slack.data

  // Set custom data key/values
  req.slack.set('whatever_you_want', { isCool: true })

  // Get the custom data
  req.slack.get('whatever_you_want')
  
  // access the object directly
  req.slack.data.whatever_you_want

  // update the whole thing - be careful though :)
  const data = Object.assign({}, data, { isCool: true })
  req.slack.update(data)
})
Custom Path
The default DataStore is JSON and written to file at .data/workspaces. You can change this by passing in a new path to the datastore setting.
new Slack({
  scope: SCOPE,
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  signing_secret: SIGNING_SECRET,
  datastore: 'data/all-my-dataz.json'
})
Custom DataStore
If you want something a little more powerful than a filestore, you can pass in your own custom DataStore object to datastore and Slack DevKit will attempt to use that instead.
class CustomDataStore {
  constructor() {
    this.data = {}
  }

  get(id) {
    return Promise.resolve(this.data[id])
  }
  
  save(id, record) {
    this.data[id] = record
    return Promise.resolve(this.data[id])
  }

  update(id, record) {
    Object.assign(this.data[id], record)
    return Promise.resolve(this.data[id])
  }
}

new Slack({
  scope: SCOPE,
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  signing_secret: SIGNING_SECRET,
  datastore: new CustomDataStore()
})

Sample Application

Here's a sample application using some commonly used Slack features
const { SCOPE, CLIENT_ID, CLIENT_SECRET, SIGNING_SECRET } = process.env
const Slack = require('slack-devkit')

// Configure express with the Slack App settings
const { server, client } = new Slack({
  scope: SCOPE,
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  signing_secret: SIGNING_SECRET
})

// All GET routes redirect to the “Add to Slack” OAuth flow
server.get('/', (req, res) => {
  // the req.slack object contains information about the request
  // and the workspace's authentication information
  const { data, app_url } = req.slack

  // Make an authenticated request to the Slack API
  req.slack.api('chat.postMessage', {
    channel: data.installer_user.app_home,
    text: 'Thanks for installing me :bow:'
  })

  // open the Slack client to the App Home
  res.redirect(app_url)
})

// Slash Command and Events API routes automatically load the
// workspace info and related datastore
server.post('/slash-command', (req, res) => {
  // check if a sub-command was sent
  const isUpload = req.slack.isSubCommand('upload')
  
  // test the message text with regex
  const containsUrl = req.slack.match(/https?:\/\//i)

  // use the included client to call other APIs or
  // to load information from other sites
  if (isUpload && containsUrl) {
    // get the arguments from a sub-command
    const fileUrl = req.slack.subcommand('upload')
    client.get(fileUrl).then(r => {
      const file = Buffer.from(r.data, 'utf8')
      
      // one-line of code to respond to slash commands with a file
      req.slack.upload({ filename: 'logo.png', file })
    })
  }

  res.send()
})


// All POST routes expect Slack callback events
// and verify against the verification token
server.post('/', (req, res) => {
  // check the event type with is()  
  const isUnfurl = req.slack.is('link_shared')
  const isMessage = req.slack.is('message.app_home')
  
  // respond with an unfurl easily
  if (isUnfurl) {
    req.slack.unfurl({ 
      text: "A successful unfurl!", 
      image_url: "https://image.com" 
    })
  }

  // reply to messages everywhere
  if (isMessage) {
    req.slack.reply({ 
      text: "Hey! I got your message :sunglasses:"
    })
  }

  res.send()
})

// Start the webserver on port 3000
server.start(3000)
Found a documentation issue? Tell us!
Fork me on GitHub