Next.js + Vercelでバックグラウンド処理したくてInngest使ってみたら便利だった話
こんにちは。
Next.jsの記事を書いていて、Rustの記事を1年以上ドラフトにしたままだったことに気付いて、あららら、と思っているmasamikiです。
自社のポータルをNext.jsで作っており、Vercelにホスティングして運用していているのですが、SlackのSlash Commandを受けるようにしたく、APIを用意していて気が付きました。
SlackのSlash Commandのtimeoutはえぇ。
ちょい重たい処理、というかChatGPTのAPIを実行させているのですが、あぁ、これはバックグラウンドで処理させるかPubSub的な用意してやらないと厳しいなと。
とりあえず、レスポンスを先に返して非同期に処理さとけばええんでは?と、こんな感じで書いてみたら、
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const api = new ChatGPTAPI({
apiKey: process.env.OPENAI_API_KEY as string,
});
api.sendMessage(event.data.text as string).then((gptRes: ChatMessage) => {
axios.post(event.data.response_url, {
response_type: 'in_channel',
text: gptRes.text,
}).then(successCallback).catch(failedCallback);
}).catch(failedCallback);
res.status(200).json({
response_type: 'in_channel',
text: 'ちょっと待ってね。',
});
}
ローカルでは、うまく行きました、が、、、、
Vercelにdeployしてみるとresponseを返した直後に殺されるもよう。
Functionってそんな感じよなぁと、断念。
追記
ご情報いただいたので追記。
waitUntil()
で待ってくれるとのこと。
こっちのが楽でしたね、お恥ずかしい🔰
さらに追記
この箇所でのコメントをいくつかいただいのですが、こういう理由です。
Shohei Maedaさん、おりばーさん、リプありがとうございました。
Inngest
Inngestは、Ship Background Jobs, Crons, Webhooks, and Reliable Workflows in record time
と書かれていますが、こういったServerlessな構成のためにバックグラウンドでの処理やCronを可能にしてくれるサービスだそうです。
VercelのマーケットプレイスにもVercelと連携できるよう、
というような感じで置いてあります。
準備
フロントで扱うために、まずInngestのサービスから二つの値を取得しておく必要があります。
-
シークレットキー
サインアップしたら、Secretsページを見に行きましょう。置いてます。
-
イベントキー
Sourcesページにいって、Create Event Keyボタンを押すと、イベントが作成されます。
あとは、キーが見えてるのでコピーするだけです。(イベント名は適当に編集)。
実装
Next.js前提での実装方法です。
パッケージの追加
yarn add inngest
とyarn add --dev inngest-cli
を実行して、パッケージを入れておきます。
inngest-cliというのが、ローカルで動かす時に、ダミーザーバーを用意してくれるやつです。
InngestのFunctionの追加
まず、バックグラウンドでやりたい処理を書きます。
公式のドキュメントではルート配下に直接inngestディレクトリを作成して、その中にファイルを置いてく感じでしたが、srcにまとめたい感があったので、僕は./src/inngest
というディレクトリを作成して、そこにFunction毎にディレクトリを切って入れてくことにしました。
import { ClientOptions, Inngest } from 'inngest';
import { ChatGPTAPI } from 'chatgpt';
import axios from 'axios';
const chatgptInngestOptions: ClientOptions = {
name: 'chatgpt',
eventKey: process.env.INNGEST_EVENT_KEY as string,
};
const inngest = new Inngest(chatgptInngestOptions);
const chatgpt = inngest.createFunction(
{ name: 'ASK A QUESTION TO CHATGPT' }, // 好きな名前つけましょう。
{ event: 'app/chatgpt.ask' }, // 任意のイベント名をつけます。呼び出す時に使います。
async ({ event }) => {
const api = new ChatGPTAPI({
apiKey: process.env.OPENAI_API_KEY as string,
});
const gptRes = await api.sendMessage(event.data.text as string);
await axios.post(event.data.response_url, {
response_type: 'in_channel',
text: gptRes.text,
});
return gptRes.text;
}
);
export { chatgptInngestOptions };
export default chatgpt;
INNGEST_EVENT_KEYが、先ほど作成したイベントキーが入るとこですね。
localで動かす時は、INNGEST_EVENT_KEY=local
にしておきます。
サーバーを追加
FunctionのサーバーをAPIのディレクトリ配下に追加します。
serverの引き数に作ったFunctionを入れてあげることで提供されるようになります。
import { serve } from 'inngest/next';
import { Inngest, RegisterOptions } from 'inngest';
import chatgpt, { chatgptInngestOptions } from '../../../inngest/chatgpt';
const inngest = new Inngest(chatgptInngestOptions);
const options: RegisterOptions = {};
if (process.env.NODE_ENV !== 'development') {
options.signingKey = process.env.INNGEST_SIGNING_KEY;
}
export default serve(inngest, [chatgpt], options);
INNGEST_SIGNING_KEYが、先ほど取ってきたシークレットキーが入るとこですね。
localでは不要なので、conditionを作ってoptionsに入れないようにしてます。
Functionを叩くAPIの追加
Cronを作るも良しですが、今回はAPIが叩かれた際に動かしたいので、そのAPIを作ります。
すでに作ったInngestの初期化のための引き数は使い回したくimport。
import type { NextApiRequest, NextApiResponse } from 'next';
import { Inngest } from 'inngest';
import { chatgptInngestOptions } from '~/inngest/chatgpt';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const inngest = new Inngest(chatgptInngestOptions);
await inngest.send({
name: 'app/chatgpt.ask', // Functionにつけたイベント名を入れます。
data: {
api_app_id: req.body.api_app_id,
response_url: req.body.response_url,
text: req.body.text,
}, // Functionの event に入るデータです。
});
res.status(200).json({
response_type: 'in_channel',
text: 'ちょっと待ってね。',
});
};
export default handler;
ローカルで実行
ローカルで実行するときは、inngestのダミーサーバーをinngest-cliで立ち上げておく必要があるので、
yarn inngest-cli dev
で立ち上げます。
で、
yarn dev
Next.jsを立ち上げます。(書かなくてもいいか)
あとは、APIを叩いてみてください。
Vercelで実行
VercelにNext.jsのアプリがデプロイされていることが前提です。
マーケットプレイスでInngestを探して、
自分のプロジェクトにIntegrateしましょう!
そして、シークレットキーやらイベントキーやらをVercelの環境変数に設定しましょう。
あとは、APIを叩いてみてください。
感想
Vercelをはじめて触ったときに、こんなにも簡単にアプリケーションがリリースできる時代になったのか、と思いましたが、バックグラウンドで実行したい処理やCronまでこれだけでできてしまうと、もう、、、なんだかなぁ、という気分になりました。
PMFや社内用のサービスにはこういうの使ってガンガン作っていけるなぁ。
良い時代になりましたなぁ。
Discussion