チュートリアル:チャットハブ付きのシンプルなチャットアプリを作る

このチュートリアルでは、Web PubSubチャットハブを使ってシンプルなリアルタイムチャットフローを構築します。

あなた:

  • クライアントアクセスURLを発行するバックエンドサーバーを設置してください
  • クライアントをWeb PubSubチャットハブに接続
  • ルームを作成する
  • メッセージの送受信
  • ルーム メンバーを管理

最終的には、Azure Web PubSubがサポートした動作するチャット体験が得られます。

前提条件

  • Azure サブスクリプション
  • Node.js 18 以降

チャットハブ付きのWeb PubSubリソースを作成

Azure Web PubSubリソースを作成し、demo-chatという名前のチャットハブを設定しましょう。

依存関係のインストール

サーバーの依存関係

npm install express @azure/web-pubsub @azure/web-pubsub-express

クライアント依存関係

npm install @azure/web-pubsub-chat-client

ステップ1:バックエンドサーバーの作成

バックエンドサーバーは以下の責任を負います:

  • ユーザーの認証
  • クライアントアクセスURLの発行

サーバーコード

import express from 'express';
import { WebPubSubServiceClient } from '@azure/web-pubsub';
import { WebPubSubEventHandler } from '@azure/web-pubsub-express';

const hubName = 'demo-chat';
const port = process.env.PORT || 3000;

const connectionString = process.env.WEB_PUBSUB_CONNECTION_STRING;
if (!connectionString) {
  throw new Error('WEB_PUBSUB_CONNECTION_STRING is not set');
}

const app = express();

const serviceClient = new WebPubSubServiceClient(
  connectionString,
  hubName,
  { allowInsecureConnection: true }
);

この段階が存在する理由

Web PubSubサービスは匿名接続をサポートしていますが、最も一般的な本番パターンは サーバー発行のアクセスモデルを使用しています。

サーバーはユーザーのアイデンティティと権限をエンコードする 時間制限付きのクライアントアクセスURL を生成します。 この方法の特徴は次のとおりです。

  • 認証情報を安全に保つ
  • アプリが認証と承認を制御できるようにします
  • 企業のセキュリティ期待に沿った

ステップ2:ネゴシエイトエンドポイントを追加する

ネゴシエイトエンドポイントはチャットクライアントが接続するために使うクライアントアクセスURLを返します。

app.get('/negotiate', async (req, res) => {
  console.log(`received negotiate request: ${JSON.stringify(req.query)}`);

  const userId = req.query.userId;
  if (!userId) {
    return res.status(400).send('Missing userId');
  }

  const token = await serviceClient.getClientAccessToken({
    userId,
  });

  res.json({
    url: token.url,
  });
});

この段階が存在する理由

チャットクライアントは特定のユーザーとして接続しなければなりません。

ネゴシエイトエンドポイントとは、アプリケーションが以下の場所です:

  • アプリレベルのアイデンティティをチャットユーザーにマッピングします
  • スコープ付きの一時アクセスURLを発行します
  • 誰が接続できるかを決める

本番環境では、このエンドポイントは通常以下のように扱われます:

  • 認証(クッキー、ヘッダー、トークン)を検証します
  • 認可ルールを適用

ステップ3:サーバー開始

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

バックエンドはクライアント接続を受け入れる準備ができています。

ステップ4:クライアントをチャットハブに接続する

クライアント側ではサーバーからアクセスURLを取得し、 demo-chat ハブにログインします。

import { ChatClient } from '@azure/web-pubsub-chat-client';

// Fetch a fresh client access URL from the negotiate endpoint.
const getClientAccessUrl = (userId) =>
  fetch(`/negotiate?userId=${userId}`)
    .then(r => r.json())
    .then(d => d.url);

// Option 1: start with a one-time client access URL.
const alice = await ChatClient.start(await getClientAccessUrl('alice'));
console.log(`Started as: ${alice.userId}`);

// Option 2: start with a credential so the client can refresh the URL itself.
const charlie = await ChatClient.start({
  getClientAccessUrl: () => getClientAccessUrl('charlie'),
});
console.log(`Started as: ${charlie.userId}`);

この段階が存在する理由

Web PubSubチャットハブはWeb PubSubの接続モデルを基に構築されています。 この認証ステップ:

  • リアルタイム接続の確立
  • 接続をユーザー識別と関連付けます
  • チャット専用の部屋やメッセージ履歴などの機能を有効にします

ステップ5:チャットイベントを聞く

リスナー登録はリアルタイムの更新を受け取ります。

alice.on('message', (event) => {
  const msg = event.message;
  console.log(`Alice received: ${msg.createdBy}: ${msg.content.text}`);
});

alice.on('room-joined', (event) => {
  console.log(`Alice joined room: ${event.room.title}`);
});

charlie.on('message', (event) => {
  const msg = event.message;
  console.log(`Charlie received: ${msg.createdBy}: ${msg.content.text}`);
});

charlie.on('room-joined', (event) => {
  console.log(`Charlie joined room: ${event.room.title}`);
});

チャットクライアントはイベントエミッターです。 messageroom-joinedに加え、room-leftmember-joinedmember-leftstartedstoppedも聴くことができます。 同じ引数で off を使い、リスナーを削除してください。

この段階が存在する理由

チャットは本質的にイベント駆動型です。 これらのリスナーを使用すると、アプリケーションで次のことができます:

  • 受信メッセージに反応する
  • ユーザーが部屋に参加したときにUIを更新してください
  • 複数のデバイスやブラウザのタブで同期を保つ

ステップ6:部屋を作成しメッセージを送信

部屋を作成し、初期メンバーを追加してください:

const room = await alice.createRoom('My Room', ['charlie']);

部屋にメッセージを送りましょう:

await alice.sendToRoom(room.roomId, 'Hello!');

メッセージはリアルタイムで全室のメンバーに配信されます。

この段階が存在する理由

チャットルームはチャットを整理します:

  • 誰がメッセージを受け取るかを定義します。
  • 彼らはメッセージ履歴を管理しています。
  • これによりチャットは1対1のメッセージングを超えて拡張できます。

ステップ7:メッセージ履歴を取得する

部屋から過去のメッセージを取り戻す。 listRoomMessages ページ化された非同期イテレーターを返すので、すべてのメッセージを直接反復処理できます:

for await (const msg of alice.listRoomMessages(room.roomId)) {
  console.log(`${msg.createdBy}: ${msg.content.text}`);
}

または履歴を1ページずつ読み込む(例えば「スクロールアップで50ページ読み込み、さらに50ページ」など):

const pages = alice.listRoomMessages(room.roomId).byPage({ maxPageSize: 50 });
const firstPage = await pages.next();
const messages = firstPage.value ?? [];

この段階が存在する理由

新たに接続したクライアントは、しばしばコンテキストを必要とします。

メッセージ履歴はアプリに以下を可能にします:

  • 既存のメッセージをレンダリングする
  • 再接続後にチャットを再開してください
  • マルチデバイス利用のサポート

ステップ8:部屋のメンバーを管理する

ルームにユーザーを追加する:

await alice.addUserToRoom(room.roomId, 'bob');

ユーザーを部屋から削除する:

await alice.removeUserFromRoom(room.roomId, 'bob');

メンバーの変更は即時に適用されます。

ステップ9:片付け

クライアントがメッセージを受け取る必要がなくなった場合:

await alice.stop();
await charlie.stop();

作成した内容

このクイック スタートでは、次の操作を行います。

  • サーバーから安全なクライアントアクセスURLを発行しました
  • クライアントをチャットハブに接続
  • チャットルームの作成と参加
  • リアルタイムでの送受信メッセージ
  • 読み込まれたメッセージ履歴
  • 管理対象のルーム メンバーシップ

WebSocketサーバー、ファンアウトロジック、メッセージ永続性の管理は不要です。