Help us understand the problem. What is going on with this article?

Bolt for JavaScript を使った Slack アプリ開発で知っておくと捗る 7 つのこと

Bolt とは?

Slack でユーザとのインタラクション(メッセージ内の Block Kit のボタンのクリック、プルダウンでの選択、モーダルやダイアログからのデータ送信、Home タブの制御 etc)を処理したり、Events API を subscribe したりする Slack アプリを作る場合、Slack からリクエストを受け付ける Web サービスを立ててあげる必要があります。

この Web サービスを Node.js で簡単に、かつ迷わず作るためのフレームワークとして、2019 年に Slack から公開されたのが Bolt for JavaScript です。この辺の記事もみてみてください。

知っておくとちょっと便利な 7 つのこと

Bolt は比較的簡単に扱えるフレームワークだと思いますが、知っておくと便利かなということがいくつかあるので、それを共有したいと思います。

  • 再起動なしで変更を反映
  • .env の活用 & 強制上書き
  • ログレベルの変更
  • リクエスト情報を丸ごとログ出力
  • FaaS で動かすなら processBeforeRepsonse: true を設定
  • サンプル・テンプレート
  • コミュニティ

なお、これから説明することの多くは以下のリポジトリに盛り込んであります。コードを見る方が早いという方はこちらを見てください。もしもこのテンプレートが何かの役に立ったなら :star: を増やしておいてください :smiley:

https://github.com/seratch/bolt-starter

プロジェクトを始めるのに必要な手順を示した 日本語の README もありますので、そちらも参考にしてみてください。

再起動なしで変更を反映

まず、基本的なところから。コードを書きながら動作を確認するときにいちいち再起動していては効率が悪すぎます。

シンプルな Bolt アプリであれば、以下のように npm run local (名前は違うものでもよいでしょう)のような感じで nodemon を使うのが簡単でわかりやすいですね。実際に運用するときは npm start で起動させます。

{
  "scripts": {
    "local": "node_modules/.bin/nodemon index.js",
    "start": "node index.js"
  },
  "dependencies": {
    "@slack/bolt": "^2.0.1",
    "dotenv": "^8.2.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.1"
  }
}

TypeScript の場合、webpack を使われているケースも多いですが、シンプルにやるなら nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts のようにできるかと思います。

TypeScript で使う場合は、こちらの記事も参考にしてみてください。

.env の活用 & 強制上書き

これは Bolt 固有ではないですが、Node.js のアプリで、環境変数の参照だけでなく .env というファイルからそれらの情報を読み込むために dotenv という npm パッケージが広く使われています(使わないといけないわけではないです)。

ただ、この dotenv は同じキー名が環境変数にもある場合、.env での設定は無視されます。ローカルでの開発の場合だと上書きしたいユースケースも多いかと思います。その場合は以下のようなコードをそのまま貼り付けておくと .env を正として上書きしてくれるので便利かもしれません。

const config = require("dotenv").config().parsed;
for (const k in config) {
  process.env[k] = config[k];
}

また .gitignore.env も追加することを忘れずに。.env.sample とか _env のような名前で雛形を置いておくのもよいですね。

ログレベルの変更

デフォルトだと Bolt は INFO レベルでログを出力します。デバッグしているときに Bolt の内部で出力されているログをもう少し見たいというときは、以下のような感じで、logLevel を渡します。

const { App } = require("@slack/bolt");
const { LogLevel } = require("@slack/logger");

const app = new App({
  logLevel: LogLevel.DEBUG,
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

ただ、実際には app.command("/echo", fn)fn にあたるリスナーの関数内でも、同じ設定の Logger を使ってログを出したいというユースケースは多いでしょうから、Logger ごと外で初期化して渡してあげるのもよいでしょう。

const { App } = require("@slack/bolt");
const { LogLevel, ConsoleLogger } = require("@slack/logger");

const logLevel = process.env.SLACK_LOG_LEVEL || LogLevel.INFO;
const logger = new ConsoleLogger();
logger.setLevel(logLevel);

const app = new App({
  logger: logger,
  logLevel: logLevel,
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

app.event("app_mention", async ({ event }) => {
  logger.debug(`${event.user} mentioned me!`);
});

なお、@slack/web-api version 5.6.0 時点でこのような既知の改善ポイントがあり、ロガーの名前が固定で WebClient と表示されます。私の修正が最新のリビジョンには反映済ですので、次の @slack/web-api のリリースから解消される予定です :bow:

あともう一つ、将来のバージョンでロガーをリスナー関数の引数として取得できるようにするアイデアがあります。賛同される方はこちらの issue:thumbsup: やコメントをしていただくとよいと思います。

リクエスト情報を丸ごとログ出力

Slack からペイロードを処理していて「あの項目はリクエストのどの階層にどんなキー名で存在しているのかな? :thinking: 」と少し困ったことがあるかもしれません。そもそも API ドキュメントがもっと網羅的になるべき・・ということは前提として、利用側で今すぐに簡単にできる工夫を一つご紹介します。

ローカルでの開発中限定で、以下のような Middleware を設定してみましょう。

if (process.env.SLACK_REQUEST_LOG_ENABLED === "1") {
  app.use(async (args) => {
    const copiedArgs = JSON.parse(JSON.stringify(args));
    copiedArgs.context.botToken = 'xoxb-***';
    if (copiedArgs.context.userToken) {
      copiedArgs.context.userToken = 'xoxp-***';
    }
    copiedArgs.client = {};
    copiedArgs.logger = {};
    args.logger.debug(
      "Dumping request data for debugging...\n\n" +
      JSON.stringify(copiedArgs, null, 2) +
      "\n"
    );
    const result = await args.next();
    args.logger.debug("next() call completed");
    return result;
  });
}

すると、以下のような出力を見ることができるようになります。これによって、リスナー関数で使える値の構造を簡単に把握することができますし、うまくルーティングされないときのデバッグも楽になるかと思います。

{
  "context": {
    "botToken": "xoxb-***",
    "botUserId": "U1234567",
    "botId": "B1234567"
  },
  "body": {
    "type": "block_actions",
    "team": {
      "id": "T1234567",
      "domain": "your-subdomain"
    },
    "user": {
      "id": "U1234567",
      "username": "seratch",
      "name": "seratch",
      "team_id": "T1234567"
    },
    "api_app_id": "A1234567",
    "token": "random",
    "container": {
      "type": "message",
      "message_ts": "12345.12345",
      "channel_id": "C1234567",
      "is_ephemeral": true
    },
    "trigger_id": "12345.12345.random",
    "channel": {
      "id": "C1234567",
      "name": "general"
    },
    "response_url": "https://hooks.slack.com/actions/T1234567/12345/random",
    "actions": [
      {
        "action_id": "action-id",
        "block_id": "block-id",
        "text": {
          "type": "plain_text",
          "text": "Button",
          "emoji": true
        },
        "value": "click_me_123",
        "type": "button",
        "action_ts": "12345.12345"
      }
    ]
  },
  "payload": {
    "action_id": "action-id",
    "block_id": "block-id",
    "text": {
      "type": "plain_text",
      "text": "Button",
      "emoji": true
    },
    "value": "click_me_123",
    "type": "button",
    "action_ts": "12345.12345"
  },
  "action": {
    "action_id": "action-id",
    "block_id": "block-id",
    "text": {
      "type": "plain_text",
      "text": "Button",
      "emoji": true
    },
    "value": "click_me_123",
    "type": "button",
    "action_ts": "12345.12345"
  },
  "client": {},
  "logger": {}
}

あくまでローカルでのデバッグのときのみ有効となるように switch できるフラグを上記のように環境変数などで渡すとよいでしょう。

FaaS で動かすなら processBeforeRepsonse: true を設定

AWS Lambda や Google Cloud Functions のような FaaS (Function as a Service) 環境で動かすなら v2.0 から導入された processBeforeResponse というフラグを true に設定してください。これを設定することでリスナー関数が 3 秒以内に処理を完了できる前提で FaaS 環境で正常に動作させることができるようになります。

より詳細な内容を知りたい方は、英語のみとなりますが、私が書いている GitHub issue とそこからリンクされている議論や pull request を参考にしてください。 https://github.com/slackapi/bolt/issues/361

const { App } = require("@slack/bolt");

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  processBeforeResponse: true,
});

app.command("/hello", async ({ ack, respond }) => {
  await respond("What's up?");
  await ack(); // processBeforeResponse: true の場合、これより前の await つきの処理も全てランタイムが破棄される前に実行されることが保証される
});

サンプル・テンプレート

いくつか参考になりそうなサンプルやテンプレートを紹介しておきます。

以下はプロジェクトの雛形です。

コミュニティ

Slack Platform Community という Slack のプラットフォーム機能を活用する人たちが集まるコミュニティがあります。slackcommunity.com に参加すると、新しいイベントが公開されたときに通知が来ます。

日本では meetup.com より connpass が広く使われているので、日本のチャプターでは connpass でもミラーのイベント情報が流れるようになっています。

そして、普段のオンラインのコミュニケーションには Slack ワークスペースがあります。こちらから参加することができます。今年始まったばかりですが、すでに活気のあるコミュニティになってきています!

年末年始は Bolt 入門で決まり(?)

以上、Bolt に関するいくつかの tips を紹介しました。

「年末年始に新しいことを始めたい」という人は、Bolt を使った Slack アプリ開発に入門してみてはどうでしょうか?

日々をちょっと楽しくしたり、作業の効率を良くするためのアプリをつくってみてください!

seratch
Slack の東京オフィスで技術スタッフをしています。日本でのパートナー協業を技術面でサポートしたり、本社のメンバーと Slack の SDK 開発を一緒に行ったりしています。
http://git.io/sera
slack
Slack は必要なメンバーから情報、ツールまで一元化するビジネスコラボレーションハブと、そのアプリ開発用プラットフォームを提供しています。
https://api.slack.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした