chat-room について
RivetKit でリアルタイムチャットルームのバックエンドを構築する開発者向けのスキルです。部屋ごとに Actor を割り当て、SQLite に履歴を保存し、接続中のクライアントへイベントで新着メッセージを配信します。
主な機能
- 01部屋ごとの Actor 構成
- 02SQLite でメッセージ履歴を保存
- 03メッセージ ID 順の履歴取得
- 04新着メッセージイベントの配信
- 05React フロントエンドの例
活用シーン
- 01部屋キーごとに分離されたチャットルームの構築
- 02部屋のメッセージ履歴の保存と読み込み
- 03接続中のクライアントへのメッセージ配信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rivet-dev/ skills --skill chat-roomインストール後、Claude Code で/chat-roomを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
BASE_SKILL.mdindex.jsonopenapi.jsonreference/actors/access-control.mdactions.mdactor-runtime-socket.mdappearance.mdauthentication.mdcommunicating-between-actors.mdconnections.mddebugging.mddesign-patterns.mddestroy.mderrors.mdevents.mdfetch-and-websocket-handler.mdhelper-types.mdhttp-api.mdinput.mdinspector-tabs.mdkeys.mdkv.mdlifecycle.mdlimits.mdmetadata.mdqueues.mdreference/actors/quickstart/backend.mdcloudflare.mdeffect.mdnext-js.md詳細
チャットルーム
重要:何かを始める前に、このスキルのディレクトリにある BASE_SKILL.md を必ず読んでください。デバッグ、エラー処理、状態管理、デプロイ、プロジェクトのセットアップに関する重要なガイダンスが記載されています。これらのルールとパターンは、すべての RivetKit の作業に適用されます。以下の内容は、すでに読み、理解していることを前提としています。
動作するサンプル
参考実装が必要な場合は、以下のテンプレートにある動作するサンプルのソースコードを読んでください。
RivetKit でチャットルームのバックエンドを構築するパターン:ルーム単位のアクター、永続的なメッセージ履歴、WebSocket 接続を介したリアルタイム配信。
スターターコード
GitHub の動作するサンプルを出発点として、必要に応じて変更してください。バックエンドは単一の chatRoom アクターで、フロントエンドは @rivetkit/react を使用する React アプリです(React のクイックスタートを参照)。
| トピック | 概要 |
|---|---|
| ルームモデル | ルームごとに 1 つの chatRoom アクターを割り当てます。フロントエンドでは、キーのデフォルト値は general です。別のルーム名を入力すると、別のアクターに接続します。 |
| 履歴 | db({ onMigrate }) で SQLite の messages テーブルを作成し、ORDER BY id ASC で読み込みます。 |
| 配信 | sendMessage は行を挿入した後、接続中のすべてのクライアントに型付きの newMessage イベントをブロードキャストします。 |
| ID | サンプルでは扱いません。sender は通常のアクターアクションの引数です。本番環境では、ID を接続に紐付ける必要があります。 |
ルームごとのアクターモデル
各ルームは、キーで指定する 1 つの Rivet アクターインスタンスです。クライアントは useActor({ name: "chatRoom", key: [roomId] }) を呼び出し、そのルームのアクターを取得または作成します。これにより、次のような利点があります。
- 分離:各ルームの履歴と接続は、そのキーの範囲に完全に限定されます。ルームの入力値を切り替えると、フックのキーが変わり、別の履歴を持つ別のアクターに接続します。
- 単一の直列化された書き込み元:1 つのルームに対する
sendMessageの呼び出しはすべて、1 つのアクターを通じて実行されるため、ロックなしでメッセージの順序が一貫します。SQLite のAUTOINCREMENTID が正規の順序となるため、getHistoryはタイムスタンプではなくidで並べ替えます。 - 自然なスケーリング:ルームはクラスター全体に個別に分散します。負荷の高いルームが、ほかのルームの処理を遅らせることはありません。
メッセージ履歴の保存
このサンプルでは、履歴を JSON 状態ではなく、アクターの SQLite データベースに保存します。履歴の量とクエリのニーズに応じて選択してください。
| 方法 | 使用する場面 | 実装ガイダンス |
|---|---|---|
| SQLite(このサンプルで使用) | 並べ替え、上限設定、ページネーション、検索が必要な、大量または長期間保持する履歴 | db({ onMigrate }) で messages テーブルを作成し、パラメーター化クエリ(c.db.execute("INSERT ... VALUES (?, ?, ?)", ...))で挿入して、ORDER BY id ASC で読み込みます。履歴はアクターがスリープしても保持され、メモリに保存したい量を超えてスケールできます。 |
| JSON 状態 | 直近の少量の履歴。たとえば、直近の 50~100 件のメッセージ | アクター状態の messages 配列に追加し、送信のたびに上限を超えた分を削除します。最も簡単な方法ですが、履歴全体がメモリに保存され、クエリレイヤーもないため、件数を制限した直近の履歴を扱う用途に限られます。 |
ブロードキャスト配信
新しいメッセージは、型付きのイベントを通じて接続中のクライアントに届きます。
- アクターは
events: { newMessage: event() }を宣言します。Messageは{ sender, text, timestamp }です。 sendMessageアクションはサーバー側でDate.now()のタイムスタンプを付けてメッセージを作成し、messagesテーブルに挿入した後、c.broadcast("newMessage", message)を呼び出し、呼び出し元にメッセージを返します。- 各クライアントは
useEvent("newMessage", ...)で購読し、ローカルのリストに追加します。送信者も、ほかの全員と同じブロードキャスト経路で自分のメッセージを表示するため、すべてのクライアントが同じ処理経路を使います。 - 履歴の読み込みは接続状態に依存します。接続の準備ができたら、クライアントは一度だけ
getHistory()を呼び出して過去のメッセージを表示し、その後はイベントを利用します。
ルーム全体へのメッセージにはc.broadcast(...)を使用します。DMなど、非公開または宛先ごとのペイロードを送信する場合は、個別の接続に送信してください。これは、この例に対する推奨拡張です。
タイピングインジケーターとプレゼンス(拡張)
この例では、タイピングインジケーター、プレゼンス、参加・退出処理は実装していません。コードにはcreateConnState、onConnect、onDisconnectはありません。これらが必要な場合は、一時的な接続の動作として追加してください。
- 一時的な状態にする:ユーザー名とタイピングフラグは接続ごとの状態に保存し、SQLiteや永続化されたアクター状態には保存しないでください。プレゼンスはライブ接続から導出されるもので、接続が切れたら消える必要があります。
- 変更時のみブロードキャストする:ポーリングや定期的な更新ではなく、ユーザーが入力を開始または停止したときにタイピングイベントを、
onConnect/onDisconnectからプレゼンスイベントを送信します。 - クライアント側で期限切れにする:接続が切れても「入力中」の表示が残らないよう、クライアント側で短いタイムアウト後にタイピングインジケーターを解除します。
ユーザーごとの受信トレイ(拡張)
オフライン配信、DM、未読件数、通知の一斉配信には、ユーザーごとにuserInbox[userId]アクターを追加します。これは、この例に対する拡張です。
- ルームアクターはアクター間呼び出しを通じて、各メッセージをすべてのメンバーの受信トレイアクターに転送します。これにより、ルームに接続していないユーザーにもメッセージが蓄積されます。
- 受信トレイアクターはユーザーごとの未読状態を管理し、ユーザーがオンラインになったときに、そのユーザーがどのルームにいるかに関係なく提供します。
- DMは、ユーザーIDをソートしたペアをキーにする
chatRoomか、共有履歴のセマンティクスが不要な場合は受信トレイ間の直接配信にすることで、単純なルームとして扱えます。
アクター
- キー:
chatRoom[roomId] - 役割:1つのチャットルームを管理します。ルームのメッセージ履歴をSQLiteデータベースに保存し、新しいメッセージを接続中のすべてのクライアントにブロードキャストします。
- アクション
sendMessagegetHistory
- キュー
- なし
- イベント
newMessage
- 状態
- SQLite
messagesテーブル:id(自動インクリメントの主キー)、sender、text、timestamp
ライフサイクル
sequenceDiagram
participant A as Client A
participant B as Client B
participant R as chatRoom
A->>R: connect with key [roomId]
Note over R: every start runs onMigrate (CREATE TABLE IF NOT EXISTS messages)
A->>R: getHistory()
R-->>A: Message[] ordered by id
B->>R: connect with key [roomId]
B->>R: getHistory()
R-->>B: Message[] ordered by id
A->>R: sendMessage(sender, text)
Note over R: INSERT row with server timestamp
R-->>A: newMessage (broadcast)
R-->>B: newMessage (broadcast)
A->>R: disconnect
Note over R: history stays in SQLite for the next connection
セキュリティチェックリスト
この例は意図的に最小限にしており、以下のすべてを省略しています。本番環境に導入する前に追加してください。
- 参加前の認証:クライアントはルーム名を知っていればどのルームにも参加でき、すべての呼び出しで
senderに任意のクライアント入力を指定できます。接続認証でトークンを検証し、ID を接続状態に紐付け、履歴を提供する前にルームのメンバーシップを確認してください。アクション引数として渡された送信者名を決して信頼しないでください。 - メッセージ長の制限:この例では空のメッセージが受け入れられ、長さの上限もありません。サーバー側でトリミングし、空のテキストを拒否して、最大長を設定してください。
- 接続ごとのレート制限:スパムやブロードキャストの増幅を防ぐため、接続ごとに
sendMessageのレートを制限してください。 - サーバー側のタイムスタンプと ID:この例ではすでに正しく実装されています。
timestampはアクション内でDate.now()から取得し、idは SQLite のAUTOINCREMENTから取得します。この実装を維持し、クライアントから送信されたタイムスタンプや ID は決して受け入れないでください。 - 履歴の上限:
getHistoryは上限なしですべての行を返します。LIMITとページネーションを追加し、長期間稼働するルームのデータが無制限に増えないよう、古い行を削除またはアーカイブしてください。 - パラメーター化クエリ:この例ではすでに
?プレースホルダーを使って挿入しています。ユーザーが送信したテキストを SQL 文字列の補間に決して含めないでください。
リファレンスマップ
アクター
- アクセス制御
- アクション
- アクターキー
- アクターランタイムソケット
- アクターのステータス
- 認証
- Cloudflare Workers クイックスタート
- アクター間の通信
- 接続
- カスタムインスペクタータブ
- デバッグ
- 設計パターン
- アクターの破棄
- Effect.ts クイックスタート(ベータ)
- エラー
- Fetch と WebSocket ハンドラー
- ヘルパー型
- アイコンと名前
- インメモリ状態
- 入力パラメーター
- ライフサイクル
- 制限
- 低レベル HTTP リクエストハンドラー
- 低レベル KV ストレージ
- 低レベル WebSocket ハンドラー
- メタデータ
- Next.js クイックスタート
- Node.js と Bun のクイックスタート
- キューと実行ループ
- React クイックスタート
- リアルタイム
- Rust クイックスタート(ベータ)
- スケーリングと並行処理
- スケジュールと Cron
- 状態の共有と参加
- SQLite
- SQLite と Drizzle
- Supabase Functions クイックスタート
- テスト
- トラブルシューティング
- 型
- 標準 HTTP API
- バージョンとアップグレード
- ワークフロー
CLI
クライアント
クックブック
- AI エージェント
- チャットルーム
- 共同編集テキストエディター
- Cron ジョブとスケジュールされたタスク
- テナントごとのデータベース
- VPC またはエアギャップネットワークへの Rivet のデプロイ
- ライブカーソルとプレゼンス
- マルチプレイヤーゲーム
デプロイ
- コンテナーランナー
- Amazon Web Services Lambda へのデプロイ
- AWS ECS へのデプロイ
- Cloudflare Workers へのデプロイ
- Freestyle へのデプロイ
- Google Cloud Run へのデプロイ
- Hetzner へのデプロイ
- Kubernetes へのデプロイ
- Railway へのデプロイ
- Rivet Compute へのデプロイ
- Supabase Functions へのデプロイ
- Vercel へのデプロイ
- VM とベアメタルへのデプロイ
一般
- アクターの設定
- クロスオリジンリソース共有
- LLM と AI 向けドキュメント
- エッジネットワーキング
- エンドポイント
- 環境変数
- HTTP サーバー
- ログ記録
- プールの設定
- 本番環境チェックリスト
- レジストリの設定
- ランタイムモード
- WASM とネイティブ SDK