概要
チャットやコメント欄のように、誰かが投稿した内容を、ほかのユーザーの画面へ「ほぼリアルタイム」に届けたいことがあります。ふつうの REST API は、クライアントが聞いたときにだけサーバーが答える一方通行のやり取りなので、新しいコメントを知るにはクライアントが何度も問い合わせる(ポーリングする)しかありません。
Amazon API Gateway の WebSocket API を使うと、クライアントとバックエンドの間に接続を張りっぱなしにして、サーバーからクライアントへメッセージを送ることができます。同じ要件は、GraphQL のサブスクリプションを持つ AWS AppSync でも実現できます。
この記事では、WebSocket API の仕組み(ルート・接続 ID・@connections API)と、AppSync との使い分けを整理します。実践では、WebSocket API を作って Lambda と DynamoDB につなぎ、2 つのクライアントの片方から送ったコメントがもう片方に届くことを確かめます。

クラウドおさる「新しい知らせはあるか」と何度も長老の家まで走るのは疲れるでござる。のろし台(WebSocket)を立てておけば、知らせのほうから飛んでくるのでござる。今日はそののろし台を建てるでござるよ。
この記事のメリット
- REST API と WebSocket API の違い(一方向か双方向か)を説明できるようになる
- WebSocket API のルート(
$connect・$disconnect・$default・カスタムルート)とルート選択式を理解できる - 接続 ID を保存し、
@connectionsAPI でクライアントへメッセージを送る仕組みを作れるようになる - リアルタイム通信で API Gateway の WebSocket API と AppSync を使い分けられるようになる
執筆者とキャラクター紹介
執筆者:土肥(とひ)
株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。


クラウドおさる



はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。



基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。



猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。
技術解説
REST API と WebSocket API
| 項目 | REST API | WebSocket API |
|---|---|---|
| 通信の向き | クライアント → サーバー(聞かれたら答える) | 双方向(サーバーからも送れる) |
| 接続 | リクエストごと | 接続を張ったまま(ステートフル) |
| 新しいデータの受け取り方 | クライアントが繰り返し問い合わせる | サーバーが接続中のクライアントへ送る |
| 向いている用途 | 一般的な CRUD | チャット、コメント、通知、ダッシュボードの即時更新 |
WebSocket API はステートフルなフロントエンドとして、Lambda や DynamoDB などのバックエンドを、クライアントから受け取ったメッセージの内容に応じて呼び出します。
ルートとルート選択式
WebSocket API では、受け取ったメッセージをルートに振り分けて、ルートごとに統合先(Lambda など)を決めます。
| ルート | 呼ばれるとき |
|---|---|
$connect | クライアントが接続したとき |
$disconnect | クライアントが切断したとき(切断後に呼ばれる) |
$default | どのルートにも当たらないメッセージを受け取ったとき |
カスタムルート(例: sendcomment) | ルート選択式の値がルートキーと一致したとき |
どのルートに振り分けるかはルート選択式で決めます。$request.body.action とすると、メッセージ(JSON)の action の値がルートキーになります。たとえば {"action": "sendcomment", "comment": "こんにちは"} は sendcomment ルートに入ります。


接続 ID と @connections API
クライアントが接続すると、API Gateway はその接続に接続 ID を割り当てます。バックエンドからクライアントへメッセージを送るには、接続 ID を指定して @connections API(POST https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/@connections/{connection_id})を呼びます。SDK では ApiGatewayManagementApi の PostToConnection です。
そのため、よくある作りは次のとおりです。
$connectで接続 ID を DynamoDB に保存する$disconnectで接続 ID を削除する- コメントを受け取ったら、保存している接続 ID の全員に
PostToConnectionで送る
PostToConnection を呼ぶ側には、execute-api:ManageConnections の権限が必要です。すでに切れた接続 ID に送ると GoneException(410)が返るので、そのときは保存している一覧から消します。



のろし台は、だれが見張っているか(接続 ID)を帳面に付けておかないと、知らせを送る先が分からないのでござる。来たら書き、帰ったら消す。これが肝でござるな。
AppSync との使い分け
| 項目 | API Gateway の WebSocket API | AWS AppSync |
|---|---|---|
| API の形 | 任意の JSON メッセージ | GraphQL(クエリ・ミューテーション・サブスクリプション) |
| リアルタイム配信 | 接続 ID を自分で管理し、@connections で送る | サブスクリプションで、データの変更をクライアントへ自動で配信 |
| 向いている場面 | 独自のプロトコルやメッセージで双方向通信したい | 複数のデータソースをまとめて扱い、変更をリアルタイムに反映したい |
API Gateway には GraphQL のエンドポイントを作る機能はありません。GraphQL でリアルタイム更新をしたいなら AppSync を選びます。



「リアルタイム」と聞いて REST API のポーリングや、長く張りっぱなしの TCP 接続を自作する案に飛びつかないことでござる。WebSocket API か AppSync、この 2 つが本命でござるぞ。
実践
WebSocket API dva-comments-ws を作り、$connect・$disconnect・sendcomment の 3 つのルートを、用意済みの Lambda 関数 dva-ws-handler につなぎます。CloudShell で 2 つのクライアントを接続し、片方から送ったコメントが両方に届くことを確かめます。
前提条件
- リージョン:
ap-northeast-1(東京) - 以下のリソースが作成済みであること
まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。
| リソース種別 | リソース名 | 内容 | 作り方の参考 |
|---|---|---|---|
| DynamoDB テーブル | dva-ws-connections | パーティションキー connectionId(文字列) | Amazon DynamoDB でテーブルを作り Lambda から読み書きする |
| Lambda 関数 | dva-ws-handler | Python 3.13。event["requestContext"]["routeKey"] でルートを見分け、$connect で接続 ID を保存、$disconnect で削除、sendcomment で全接続に PostToConnection で送る | AWS Lambda で関数を作成しイベント駆動で実行する |
| IAM ロール | dva-ws-handler-role | 上の関数の実行ロール。ログ出力、テーブルの PutItem・DeleteItem・Scan、execute-api:ManageConnections を許可 | ― |
作成するリソース一覧
| リソース種別 | リソース名 | 用途 |
|---|---|---|
| WebSocket API | dva-comments-ws | コメントを双方向でやり取りする API |
| ステージ | production | API の公開先 |
手順 1: WebSocket API を作る
AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。上部の検索バーに API Gateway と入力し、「API Gateway」を選択します。
「API を作成」をクリックし、「WebSocket API」の「構築」をクリックします。以下を設定して「次へ」をクリックします。
- API 名:
dva-comments-ws - ルート選択式:
request.body.action(入力欄の前に$が付いて$request.body.actionになります) - その他の項目(IP アドレスのタイプなど)はデフォルトのままにします


「ルートを追加」の画面で、「$connect ルートを追加」と「$disconnect ルートを追加」をクリックします(ボタンがルートキーの欄に変わります)。続けて「カスタムルートを追加」をクリックし、ルートキーに sendcomment と入力して「次へ」をクリックします。


「統合をアタッチ」の画面で、3 つのルートすべてについて、統合タイプ「Lambda」を選び、Lambda 関数に dva-ws-handler を指定して(一覧から選ぶか、関数の ARN を入力します)「次へ」をクリックします。
「ステージを追加」の画面で、ステージ名が production(デフォルト)になっていることを確認して「次へ」をクリックします。確認画面で「Create and deploy」をクリックすると、API が作成されて production ステージにデプロイされます。
手順 2: 接続先の URL を確かめる
作成した dva-comments-ws の左側ナビゲーションで「ステージ」を選択し、production を選択します。「WebSocket URL」(wss:// で始まる URL)と「@connections URL」(https:// で始まり /@connections で終わる URL)が表示されます。WebSocket URL をコピーしておきます。


コンソールで Lambda 統合を作ると、API Gateway から関数を呼び出す許可が関数のリソースベースポリシーに自動で追加されます。Lambda コンソールで dva-ws-handler の「設定」タブ →「アクセス許可」を開くと、「リソースベースのポリシーステートメント」に、プリンシパルが apigateway.amazonaws.com のステートメントが追加されていることを確認できます。
手順 3: 2 つのクライアントで接続する
コンソール上部の CloudShell のアイコンをクリックして、CloudShell を開きます。次のコマンドで、1 つ目のクライアントとして接続します(初回は wscat のダウンロードの確認が表示されるので y を入力します)。
npx wscat -c wss://<api-id>.execute-api.ap-northeast-1.amazonaws.com/productionConnected (press CTRL+C to quit) と表示されれば接続できています。CloudShell の右上の「アクション」→「列に分割」で画面を左右に分け、右側でも同じコマンドを実行してもう 1 つ接続します。
DynamoDB のコンソールの左側ナビゲーションで「項目を探索」を選択し、テーブルの一覧から dva-ws-connections を選ぶと、スキャンの結果に接続 ID が 2 件保存されていることを確認できます。


手順 4: コメントを送って両方に届くことを確かめる
左側(1 つ目)のクライアントで、次のメッセージを入力して Enter キーを押します。
{"action": "sendcomment", "comment": "猿山にバナナが届きました"}メッセージの action が sendcomment なので、sendcomment ルートの Lambda が呼ばれ、保存されている接続 ID の全員にコメントを送ります。左右の両方に、< {"from": "xxxx", "comment": "猿山にバナナが届きました"} のように受信したコメントが表示されます(from は送り主の接続 ID の末尾 4 文字です)。右側のクライアントは何も問い合わせていないのに、サーバーから届いています。


試しに {"action": "unknown"} を送ると、どのルートにも当たらないので、{"message": "Forbidden", …} というエラーのメッセージが返ります(今回は $default ルートを作っていないため)。
左側のクライアントで Ctrl + C を押して切断すると、$disconnect ルートの Lambda が呼ばれ、DynamoDB の接続 ID が 1 件に減ります。



聞かれてもいないのに 右側へ知らせが飛んできたでござる。来たら帳面に書き、帰ったら消し、知らせは帳面の全員へ。のろし台の仕組みがそろったのでござるな。
まとめ
| 要件 | 選ぶもの |
|---|---|
| 独自のメッセージで、サーバーからクライアントへもリアルタイムに送りたい | API Gateway の WebSocket API |
| GraphQL で、データの変更をクライアントへリアルタイムに反映したい | AWS AppSync(サブスクリプション) |
| 聞かれたら答えるだけでよい | API Gateway の REST API / HTTP API |
- WebSocket API は接続を張ったまま双方向に通信できる。REST API はリクエストごとの一方向
- メッセージはルート選択式(例:
$request.body.action)でルートに振り分ける。$connect・$disconnect・$defaultは特別なルート - サーバーからクライアントへ送るには、接続 ID を指定して
@connectionsAPI(PostToConnection)を呼ぶ。権限はexecute-api:ManageConnections - 接続 ID は
$connectで保存し、$disconnectで消す。切れた接続に送るとGoneExceptionになる - API Gateway に GraphQL のエンドポイントは無い。GraphQL でのリアルタイム更新は AppSync
参照先
- API Gateway WebSocket API – Amazon API Gateway
- API Gateway で WebSocket API のルートを作成する – Amazon API Gateway
- バックエンドサービスでの @connections コマンドの使用 – Amazon API Gateway
- チュートリアル: WebSocket API、Lambda、DynamoDB を使用して WebSocket チャットアプリを作成する – Amazon API Gateway
- AWS AppSync とは – AWS AppSync GraphQL

















