Blogs

Node.js での堅牢な Webhook サービスの構築:ベストプラクティスとテクニック

August 6, 2024

従来、アプリケーションは外部システムから更新を取得するためにポーリングメカニズムに依存していました。このアプローチは非効率的で、リソースを消費し、重要な情報の受信が遅れることもよくありました。Webhooksは、特定のイベントが発生したときに外部システムが事前にデータをアプリケーションに送信するプッシュベースのモデルを導入することで、これに革命をもたらしました。

ウェブフックとは

Webhook は基本的に、ソフトウェアアプリケーションの特定のイベントによってトリガーされる HTTP コールバックです。定義されたイベントが発生すると、ソースシステムは HTTP POST ペイロードをあらかじめ定義された URL に送信し、その発生をアプリケーションに通知します。

なぜウェブフックを使うのか?

Webhook には、従来のポーリング方法に比べていくつかの利点があります。

  • リアルタイム更新: イベントが発生するとすぐに情報を受け取れるため、遅延がなくなります。
  • サーバー負荷の軽減: 絶え間ないポーリングが不要で、リソースを節約できます。
  • リソースの効率的な利用: 特定のイベントに基づいて、必要な場合にのみアクションをトリガーします。

実際の例:

  • 注文確認書または出荷更新情報をアプリケーションに送信する電子商取引プラットフォーム
  • 取引の成功または失敗を通知する支払いゲートウェイ
  • ファイルの変更やコメントに関する通知を送信するコラボレーションツール

ウェブフックの仕組み:

  1. イベントトリガー: ソースシステムでイベントが発生しました (注文、支払い処理など)。
  2. ウェブフックリクエスト: ソースシステムは、定義済みのウェブフック URL に HTTP POST リクエストを送信します。
  3. ペイロード配信: リクエストの本文には、イベントに関するデータ (多くの場合 JSON 形式) が含まれます。
  4. ウェブフックエンドポイント: アプリケーションはリクエストを受け取り、それに応じてデータを処理します。

ウェブフックの基礎を理解したところで、Node.js で基本的な Webhook サービスを構築する準備が整いました。

ウェブフック用の Node.js プロジェクトのセットアップ

始めるには、Node.js プロジェクトが必要です。使用 npm init-y 新しいプロジェクトディレクトリをすばやく作成できます。次に、必要な依存関係をインストールします。

npm install express body-parser cors
  • Express.js: Web サーバーの構築用。
  • ボディパーサー: 受信したリクエストボディの解析用。
  • コード: 必要に応じてクロスオリジンリソースシェアリング (CORS) を処理するため。

次のステップには、イベントが発生したときにトリガーする必要のある Webhook を保存する CRUD オペレーションの作成が含まれます。

ウェブフックストレージの CRUD 操作の実装

1。Mongoose を使用して MongoDB インスタンスをセットアップ:という名前のファイルを作成します。 db.js MongoDB データベースに接続するには:

 const mongoose = require('mongoose');

 const connectDB = async () => {
   try {
     await mongoose.connect('mongodb://localhost:27017/webhook_service', {
       useNewUrlParser: true,
       useUnifiedTopology: true,
     });
     console.log('MongoDB connected successfully');
   } catch (error) {
     console.error('MongoDB connection error:', error);
     process.exit(1); // Exit process on connection failure
   }
 };

 module.exports = connectDB;

2。ウェブフックスキーマの定義:

という名前のファイルを作成 Webhook.js ウェブフックデータの Mongoose モデルを定義するには:

 const mongoose = require('mongoose');

 const WebhookSchema = new mongoose.Schema({
   url: {
     type: String,
     required: true,
   },
   headers: {
     type: Object,
     default: {},
   },
   events: {
     type: [String], // Array of strings representing subscribed events
     required: true,
   },
   createdAt: {
     type: Date,
     default: Date.now,
   },
  // Additional considerations:

   secret: {
     type: String,
     default: '', // Optional secret key for authentication
   },
   isActive: {
     type: Boolean,
     default: true, // Flag indicating if the webhook is currently active
   },
   description: {
     type: String,
     default: '', // Optional description of the webhook's purpose
   },

 });

 module.exports = mongoose.model('Webhook', WebhookSchema);

3。すべての Webhook を一覧表示するエンドポイントを定義します。

 // Read (GET) all webhooks
 app.get('/webhooks', async (req, res) => {
   try {
     const webhooks = await Webhook.find();
     res.json(webhooks);
   } catch (error) {
     console.error(error);
     res.status(500).send('Server Error');
   }
 });

4。Webhook をデータベースに保存するエンドポイントを定義します。

 // Create (POST) a new webhook
 app.post('/webhooks', async (req, res) => {
   try {
     const newWebhook = new Webhook(req.body);
     const savedWebhook = await newWebhook.save();
     res.status(201).json(savedWebhook);
   } catch (error) {
     console.error(error);
     res.status(500).send('Server Error');
   }
 });

5。ID を使用して 1 つの Webhook を取得するエンドポイントを定義します。

 // Read (GET) a single webhook by ID
 app.get('/webhooks/:id', async (req, res) => {
   try {
     const webhook = await Webhook.findById(req.params.id);
     if (!webhook) {
       return res.status(404).send('Webhook not found');
     }
     res.json(webhook);
   } catch (error) {
     console.error(error);
     res.status(500).send('Server Error');
   }
 });

6。id を使用して Webhook を更新するエンドポイントを定義します。

 // Update (PUT) a webhook by ID
 app.put('/webhooks/:id', async (req, res) => {
   try {
     const updatedWebhook = await Webhook.findByIdAndUpdate(
       req.params.id,
       req.body,
       { new: true } // Return the updated document
     );
     if (!updatedWebhook) {
       return res.status(404).send('Webhook not found');
     }
     res.json(updatedWebhook);
   } catch (error) {
     console.error(error);
     res.status(500).send('Server Error');
   }
 });

7。id を使用して Webhook を削除するエンドポイントを定義します。

 // Delete (DELETE) a webhook by ID
 app.delete('/webhooks/:id', async (req, res) => {
   try {
     const deletedWebhook = await Webhook.findByIdAndDelete(req.params.id);
     if (!deletedWebhook) {
       return res.status(404).send('Webhook not found');
     }
     res.json({ message: 'Webhook deleted successfully' });
   } catch (error) {
     console.error(error);
     res.status(500).send('Server Error');
   }
 });

Node.js でのウェブフックトリガーロジックの作成

このセクションでは、ダミーイベントを生成し、続いてこのイベントに登録されているWebhookをトリガーするエンドポイントを作成します。
このロジックに含まれるステップは以下のとおりです。

  1. 特定のイベントに登録されているすべての Webhook を取得する
  2. 各 Webhook のペイロードを定義
  3. を送る ポスト それぞれのペイロードを含む各ウェブフックへのリクエスト
app.post('/generate-event', async (req, res) => {
  try {
        const {event, data} = req.body;

        // fetch all webhooks that subscribed to this event
        const webhooks = await Webhook.find({
            events:event
        });



        // Define webhook payload
        const webhookPayload = {
            event: event,
            data: data,
        };

        // Send POST request to each webhook endpoint
        for (const webhook of webhooks) {
            await axios.post(webhook?.url, webhookPayload);
        }


        res.status(200).json({ message: 'Event generated and webhook triggered successfully' });
    } catch (error) {
        console.error('Error generating event and triggering webhook:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});

Webhook レシーバーの段階的な構築

Webhookレシーバーは基本的にリスニングサービスです。 他のアプリケーションがあなたにメッセージを送信できるメールボックスのようなものです。この例では、エンドポイントを定義しています。 /ユーザ・ウェブフック データを受信して処理し、画面に記録します。

  • 新しいルートを定義: あなたの index.js ファイル、新しいルートの作成 /ユーザ・ウェブフック 受信する Webhook リクエストを処理します。
  • リクエストボディへのアクセス: 使用 必須ボディ Webhook ペイロードで送信されたデータにアクセスします。
  • Webhook ペイロードをログに記録します。 受信した Webhook データをログに記録しておくとデバッグに役立ちます。
  • 正常に返信してください: Webhook を処理したら、送信者に成功した HTTP レスポンスを送信者に送信します。これは、サーバーがウェブフックを正常に受信して処理したことを示しています。
app.post('/webhook', (req, res) => {
  console.log('Webhook received:', req.body);
  res.status(200).send('Webhook received');
});

Webhook サービスのテスト-実用的なユースケース

Webhook サービスをテストするには、テストを以下のように 3 つのユースケースに分割する必要があります。

  1. ウェブフックの詳細を保存する: Postman などのツールを使用して、Webhook の詳細を保存する POST リクエストをエンドポイントに送信します。
  1. イベントをトリガーする: ダミーイベントを作成し、Webhook をトリガーします。
  1. 受信側エンドポイントで結果を確認: ペイロードが指定されたエンドポイントで受信されていることを確認します。

これは基本的な例です。本番環境では、Webhook のエラー処理、検証、非同期処理をより堅牢に実装する必要があるでしょう。

Webhook エンドポイントのセキュリティ保護

Webhook を扱う際には、セキュリティが最も重要です。重要な対策は次のとおりです。

  • IP ホワイトリスト: 不正アクセスを防ぐため、受信リクエストを特定の IP アドレスに制限します。
  • API キーまたはトークン: 認証には、シークレットトークンまたは API キーをリクエストヘッダーに含める必要があります。
  • HTTPS: HTTPS を使用してデータ送信を暗号化します。
  • コンテンツ検証: チェックサムまたはデジタル署名を使用して、受信する Webhook データの整合性を検証します。
  • レート制限: レート制限を実装して、悪用攻撃やサービス拒否攻撃から保護します。

受信する Webhook データの検証と処理

Webhook データを処理する前に、データの正確性とセキュリティを検証することが不可欠です。

  • データ形式の検証: 受信データが期待される形式 (JSON、XML など) に準拠していることを確認します。
  • データ整合性の検証: 欠落しているフィールドや無効なフィールドがないか確認してください。
  • データ型検証: データ型が期待されるスキーマと一致することを確認してください。
  • セキュリティチェック: 潜在的な悪質なコンテンツや攻撃を探します。

検証したら、その内容に基づいてWebhookデータを処理します。これには、データの保存、他のアクションの起動、またはアプリケーションの状態の更新が含まれる場合があります。

Webhook 実装のベストプラクティス

  • 信頼性の高い配送: 配信が失敗した場合の再試行メカニズムとデッドレターキューを実装します。
  • エラー処理: デバッグ用に有益なエラーメッセージとログエラーを提供します。
  • スケーラビリティ: 増加するトラフィックとデータ量を処理するようにWebhookサービスを設計してください。
  • セキュリティ: 認証、承認、データ暗号化などのセキュリティ対策を優先してください。
  • ドキュメンテーション: Webhook サービスを使用する開発者向けに、わかりやすいドキュメントを作成してください。

結論として、Node.js で安全で効率的な Webhook サービスを構築するには、Webhook の基礎を理解し、Node.js プロジェクトをセットアップし、CRUD 操作を実装し、強固な Webhook のトリガーおよび受信メカニズムを作成する必要があります。エンドポイントの保護、受信データの検証、信頼性の高い配信の確保などのベストプラクティスに従うことで、回復力がありスケーラブルなウェブフックサービスを構築できます。このガイドは、リアルタイムの更新とリソースの効率的な利用を実現し、最終的にアプリケーションのパフォーマンスとセキュリティを強化するのに役立つ包括的なロードマップを提供します。