question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to split listeners into different js files in bolt js?

See original GitHub issue

Description

Describe your issue here.

What type of issue is this? (place an x in one of the [ ])

  • bug
  • enhancement (feature request)
  • question
  • documentation related
  • example code related
  • testing related
  • discussion

Requirements (place an x in each of the [ ])

  • I’ve read and understood the Contributing guidelines and have done my best effort to follow them.
  • I’ve read and agree to the Code of Conduct.
  • I’ve searched for any related issues and avoided creating a duplicate issue.

Bug Report

Filling out the following details about bugs will help us solve your issue sooner.

Reproducible in:

package version:

node version:

OS version(s):

Steps to reproduce:

Expected result:

What you expected to happen

Actual result:

What actually happened

Attachments:

Logs, screenshots, screencast, sample project, funny gif, etc.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
raychariuscommented, May 27, 2021

I’ve found the best way is to have a collection of handlers that are fetched and handled in the middleware:

A RequestHandler class that maps a string that represents an event to the method that needs to handle it. It’s very simplified here, but there are a lot of things that could be stored here, such as roles the user needs to have to execute the function:

// request-handler.ts

export class RequestHandler {
  public readonly name: string;

  private readonly method: RequestHandlerFunction;

  constructor(params: RequestHandlerCreationParams) {
    this.name = params.name;
    this.method = params.method;
  }

  public async handle(args: AppRequest): Promise<void> {
    return this.method(args);
  }
}

A RequestHandlerCollection class that basically acts as a bus. Also very simplified, just returns the handler, but it could essentially call the handler and do some other things, such as logging the names of handlers that are being called (can be super helpful when using Socket Mode), etc.:

// request-handler-collection.ts

export class RequestHandlerCollection extends Collection<RequestHandler> {
  public getHandlerByName(name: string): RequestHandler | null {
    return this.findOne((item: RequestHandler) => item.name === name);
  }
}

An actual handler function that receives a request and handles it:

// render-some-modal.ts

import modals from '../ui';

export default async ({ userManager, body, context, client }) => {
  // Do stuff

  await client.views.open({
    view: modals.getSomeModal(someParams),
    trigger_id: body.trigger_id,
  });
};

Add it to the handler collection:

// handlers/index.ts

const handlerCollection = new RequestHandlerCollection();

handlerCollection.add(new RequestHandler({
  name: 'renderSomeModal',
  method: renderSomeModal,
}));

export { handlerCollection };

The Bolt middleware whose responsibility is to fetch the name of the intended handler, get the handler, do any logic necessary around the handler, such as checking roles, etc. And finally, calling the handle() function, passing in any injections from the application level:

// handle-actions-request.ts

import handlerCollection from '../handlers';
import sentryLogger from '../sentry-logger';

export default async (boltRequest) => {
  const { ack, action, context } = boltRequest;
  
  await ack();

  try {
    const handlerName = JSON.parse(action.action_id).onClick;
    const handler = handlerCollection.getHandlerByName(handlerName);
    
    // Do any logic with the handler
    
    await handler.handle({
      ...boltRequest,
      // Any other services/data/whatever you need to inject into the handler
    })
    
  } catch (error) {
    sentryLogger.error(error, {
      tags: {
        userId: context.userId,
      },
      data: {
        actionId: action.action_id,
        teamId: context.team.id,
      },
    });
  }
};

Adding this middleware to app.action for basically any block actions request.

// bolt.ts

import handleActionsRequest from './middleware';

export async function startBoltApp(app: SlackApp): Promise<App> {
  const app = new App({
    logger,
    token: app.token,
    appToken: app.appToken,
    socketMode: true,
  });
  
  
  // Add other middleware to globals or other actions 
  
  app.action({ action_id: /.*/gm }, handleActionsRequest);

  await app.start();

  return app;
}

It’s verbose, but it provides a lot of flexibility, structure, and reusable code, especially in larger applications, or projects where multiple applications live (such as all of our Slack apps at MacPaw, one codebase, multiple apps, Socket Mode).

I also do a lot of other stuff in middleware before the request hits the handler:

  • Gather any data set and persisted between modals and place that in an object persistedData.
  • Parse the view submissions into a more usable object so that it’s not parsed in each handler separately. Which of course means following conventions in the action IDs, callback IDs, but is totally worth it.
  • Etc.
1reaction
seratchcommented, May 24, 2021

Hi @AlishaK30, thanks for asking the question! I think there are several ways to organize your modules but one simple way could be like this. I know it’s not a fancy way but it works.

// --------------------------------------------
// app.js
const { App } = require("@slack/bolt");
exports.app = new App({
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
  token: process.env.SLACK_BOT_TOKEN,
});

// --------------------------------------------
// listeners.js
// You can have multiple listener enablers
exports.enableListeners = function (app) {
  app.event("app_mention", async ({ event, logger }) => {
    logger.info(event);
  });
};

// --------------------------------------------
// main.js
const { app } = require("./app");
const { enableListeners } = require('./listeners');
enableListeners(app);
(async () => {
  await app.start();
  console.log("⚡️ Bolt app is running!");
})();

We’d love to know how the folks in the community are organizing listeners!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Integrating a Bolt app into another server or framework ...
I would like to use bolt as an express middleware similar to how the @slack/events-api work. app.use('/slack/events', slackEvents.
Read more >
App interface and configuration - Slack | Bolt for JavaScript
This guide is intended to detail the Bolt interface–including listeners and their arguments, initialization options, and errors. It may be helpful to first ......
Read more >
How can I split a javascript application into multiple files?
The basic idea is that the individual JS files add to the same module with code like this: var MODULE = (function (my)...
Read more >
Build a Slackbot in Node.js with Slack's Bolt API
Develop and customize your own Slackbot using the Bolt API, ... Head into the app.js file we created earlier and add the code...
Read more >
Build a Bolt App Unit | Salesforce Trailhead
For now, you use the message() listener to listen and respond to messages. Head over to your Glitch project. Add the following to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found