This is the second blog in a five-part series.
Building a Slack app that integrates with Salesforce involves some challenges, such as knowing the right integration capability to use on each case, picking the right authorization flow, and implementing it securely. This is the second blog post of a series in which we cover the whole process of creating a Slack app that integrates with Salesforce from scratch. Learn along with us while we build Ready to Fly, our new sample app for submitting and approving travel requests in Salesforce without leaving Slack.
Note: some parts of this app have been live coded by my colleagues Mohith Shrivastava (@msrivastav13) and Kevin Poorman (@codefriar) in their codeLive video series: Building with Slack.
Check out the rest of the blogs in this series here:
- Part 1 – Architectural Overview
- Part 3 – Integrating with Salesforce
- Part 4 – Local Development and Debugging
- Part 5 – What’s Coming Next
The Bolt framework
Let’s continue our series by talking about the Bolt framework, which provides a web server to run your app on, sets up authentication with the Slack app, and gives you simplified interfaces to work with Slack APIs and features. The Bolt framework is available for Python, JavaScript, and Java. Per our familiarity with Lightning Web Components (LWC), we decided to use the JavaScript version and build a Node.js app for Ready to Fly.
How Slack apps work
If you’re familiar with web development, you’ll know that elements on a web page can react to events like “click” or “hover.” In a similar way, events occur when users perform actions in the Slack UI. For example, Slack fires events when messages are posted to a channel and when an app home is opened.
In JavaScript, you react to events by defining listeners, and it’s the same in Bolt.js. You can define listeners for events, actions, shortcuts, views, messages, and options — really different types of events. You generally write some logic in the listener, do a callout to an external system if needed, and then send changes to Slack that need to be reflected in your app’s UI.
Slack UIs are defined in JSON with a framework called Block Kit. You send a list of blocks over to a Slack API, and Slack generates the layout for you in a consistent manner. This minimizes the amount of work you have to do to create a layout, and it guarantees that all Slack apps have a consistent UX foundation. Bear in mind, Block Kit is the only way to define UIs; injecting HTML is not allowed.
Remember the app home of our Ready to Fly app?
Let’s explore the steps that are needed to render the user’s travel requests:
- The user opens the app home. The Events API publishes the
app_home_opened
event to your event subscription URL. You’ll need to set up this URL in the Slack app definition (more about this later). - If the user is authorized with Salesforce, the Node.js app requests the user’s travel requests from Salesforce, using JSforce, a JavaScript library for use with the Salesforce APIs. You’ll need to create a connected app in Salesforce to handle authentication.
- Salesforce responds by sending the user’s travel requests.
- The Node.js app generates the UI with Block Kit, and publishes it to the app home.
Let’s see another example. In Ready to Fly, an approver can see a travel request and then either approve or reject it.
Let’s see how this flow works:
- The user clicks the “Approve” or “Reject“ buttons. Slack notifies the Node.js app that the button has been pressed, sending a
block_actions
payload to your interactivity URL. This is another URL that you’ll need to set up in the Slack app definition. - If the user is authorized with Salesforce, the Node.js app sends the travel request status update to Salesforce, using JSforce.
- The Node.js app generates a modal UI with Block Kit and posts it to Slack. Slack prompts the modal to the user.
Creating the Slack app
The first step to creating a Slack app is to define its configuration at api.slack.com. This is done in a UI. You’ll need to log into your Slack workspace and then indicate your preferences for the app, such as which features do you want to activate, which events do you want the app to publish, when they occur, etc. You can configure all of this declaratively in the UI or by copying a manifest.yml file, which is very convenient and fast. For instance, this is the manifest of the Ready to Fly app that I created in my test workspace:
Notice that the Heroku app URL is set up in the event_subscriptions
request_url
property of the Slack app manifest as Slack needs to know which endpoint to notify when events happen.
The Heroku app URL must also be set up in the interactivity
request_url
property, so that Slack notifies Heroku when users interact with shortcuts, modals, or interactive components (actions).
Node.js app structure
Let’s take a look at the Node.js app structure and talk about its various folders. This will help you to navigate the app files in the examples shown in the following sections.
- listeners: where we define the listeners for events, actions, shortcuts, and views
- middleware: where we store the global middleware that we use for authentication (more about this in the next blog post)
- routes: where we create custom HTTP routes, such as the one we create so that Salesforce can post messages when the status of travel requests change
- salesforce: where we store the code to interact with Salesforce, in our case querying and performing DML operations
- store: where we created some classes to persist some data and make it available for custom HTTP routes
- user-interface: where we store the code that creates all the user interface bits in the app
- app.js: entry point for the app, where all the listeners, custom HTTP routes, and middleware are registered
Defining listeners
When using Bolt.js, the Slack event listener is a callback that you need to register in the app:
Let’s explore different types of listeners that you can create:
Events
Events occur in Slack when users perform standard actions in the Slack UI. In Bolt.js, you listen to events using the app.event()
function. For instance, here’s how we listen to the app home opened event in Ready to Fly:
Actions
When users engage with interactive UI components, such as a menu or a button, Slack fires actions (a special type of event). Note that the name of the action needs to be attached to the interactive element when it’s created.
In Bolt.js, you listen to actions using the app.action()
function. In Ready to Fly, we listen to the actions that fire when the user clicks on the buttons of our app:
Shortcuts
In Slack, you can also define shortcuts, a special entry point that can be global for the app or message scoped. Shortcuts are defined in the configuration of the Slack app, and you can use the manifest for that. In Ready to Fly, we created a global shortcut to open a modal that allows you to create travel requests.
Just like for events and actions, you can listen to shortcuts. In Bolt.js, you listen to shortcuts using the app.shortcut()
function. This is the code that listens to the create_travel_request
shortcut in Ready to Fly:
Views
When the view_submission
or view_closed
events occur, a view listener fires. This happens when the user submits a view or dismisses a modal. In Ready to Fly, we created a modal that allows users to create travel requests, and we used a view listener to execute logic when the modal is submitted. Note that the name of the view callback needs to be attached to the modal when it’s created.
In Bolt.js, you listen to view submissions using app.view()
. This is the code that listens to the submission of the initiate_travel_request
modal in Ready to Fly:
There are additional listeners that we haven’t used in Ready to Fly, such as messages, commands (as the use of shortcuts is preferred), and options. These concepts are very similar, but be sure to take a look at the documentation if you are interested in knowing more. Also, take a look at the parameters that each kind of listener receives in the reference (see the “Listener function arguments” section).
Creating user interfaces with Block Kit
To help you create Slack UIs with Block Kit, there is a tool called Block Kit Builder that will help you mock up what you want to build in advance. Indeed, you can send fancy messages to Slack directly from Block Kit Builder — nothing related to your app development, but still a useful tool!
In Bolt.js, listeners always receive a client
parameter that you can use to call the Web API methods. This way, you can easily create or modify your Slack app UIs, for instance publishing a view to the app home, opening or updating views in modals , or sending messages. Most Web API methods accept a UI that’s built with Block Kit.
For instance, in Ready To Fly, we could render some test travel requests in the app home as follows:
Executing that code, we would get this result:
However, defining everything in JSON can be tedious and error prone. That’s why we decided to go a step further and use a library for that: Block Builder. Block Builder is an open source library built by the Slack community that uses a declarative and chainable syntax to keep the JSON creation code maintainable, testable, and reusable. Using Block Builder, the code above would be equivalent to:
Much easier to read and maintain, right? I really enjoyed using this library. If you want to know more about Block Kit, I recommend that you to tackle this Trailhead module.
Defining custom HTTP routes
Custom HTTP routes provide an entry point to the Node.js app that you can call out from an external system. In other words, this allows you to expose your own HTTP API for interacting with your app. A custom HTTP route is really a standard Express router, where Express is a is lightweight HTTP server library for Node.js.
In Ready to Fly, we use a custom HTTP route to send messages from Salesforce to the Node.js app. We could have used Webhooks to post messages in the Slack app directly, but that didn’t work for our use case because we needed to execute some logic before posting the messages to Slack.
Let’s take a look at a flow in which the aforementioned custom HTTP route is involved:
- When travel request’s status changes, Salesforce makes an HTTP callout to the Node.js app. We store the Heroku app URL in a custom metadata record that’s deployed with the deploy script.
- The Node.js app checks that the message’s target user is authorized, builds the message with Block Builder, and sends it to Slack.
Note that to keep track of user sessions during the Salesforce OAuth flow dance, we took advantage of express-session
(see docs), which is an npm module to manage sessions in the server. Using express-session
forced us to use a custom ExpressReceiver. The way in which custom HTTP routes are registered in this case is slightly different from the regular one, but it’s all documented.
Wrapping up
That’s all for this blog post. I hope that you now have a good understanding of how Slack apps work, how to use Bolt.js to interact with them, and how to build Slack UIs with Block Kit. If you want to know more about this topic, check out our Slack for Salesforce Developers blog post.
Stay tuned — in the next post of the series, we’ll continue talking about Salesforce integration.
About the author
Alba Rivas works as a Principal Developer Advocate at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC.