Laravelで作るリアルタイムWebソケットアプリ:Reverb+Broadcasting実装パターン
- 作成日 2026.02.24
- その他
概要:WebSocketで「ページを再読み込みせずに更新される体験」をLaravelで組み立てる手順を、イベント設計・認可・購読・キュー運用・本番デプロイまで含めてまとめます。Reverb(Laravel公式系のWebSocketサーバ)を軸に、Broadcastingとフロント購読(Laravel Echo相当)を接続し、チャット/通知/管理画面のライブ更新を1つの構成で回せる形に落とします。
- 1. リアルタイム化で得られる要件
- 2. 全体アーキテクチャ(HTTP+Queue+WebSocket)
- 3. 準備:Broadcastingの基本設定
- 4. WSサーバ起動と疎通確認
- 5. イベント設計:ShouldBroadcastで配信する
- 6. エラーの発生条件:broadcastOnがPublicなのに秘匿情報を載せる
- 7. チャンネル認可:routes/channels.php
- 8. 送信タイミング:更新処理の直後にdispatch
- 9. フロント購読:Laravel Echo相当で受信する
- 10. Presenceで“誰が見ているか”を出す
- 11. キュー運用:配信を同期にしない
- 12. 本番デプロイ:リバースプロキシとWSS
- 13. 障害対応:よくある詰まりポイント
- 14. まとめ:Reverb+Broadcastingで“Laravelだけ”に寄せる
リアルタイム化で得られる要件
・チャット、在庫変動、審査ステータス、営業進捗などの即時反映
・通知(管理者/担当者/ユーザー)をPushで届ける
・複数オペレーターが同じ画面を見ている前提で同期する(管理画面)
・ポーリング(数秒ごとのAPI叩き)を減らして負荷を下げる
全体アーキテクチャ(HTTP+Queue+WebSocket)
Client (Browser)
├─ HTTP: 認証/初期表示/操作
└─ WS : subscribe(channels) -> receive(events)
Laravel App
├─ Event: implements ShouldBroadcast
├─ Channel Auth: routes/channels.php
├─ Queue Worker: broadcast jobs(構成次第)
└─ Reverb Server: WS接続を保持し配信準備:Broadcastingの基本設定
環境変数と接続先を揃え、WSサーバとアプリの“配信先”を一致させます。
# .env 例(名前は環境に合わせて)
BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=redis
# Reverb系(例)
REVERB_APP_ID=local
REVERB_APP_KEY=localkey
REVERB_APP_SECRET=localsecret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
# フロント側が参照するWS(例)
VITE_REVERB_HOST=127.0.0.1
VITE_REVERB_PORT=8080
VITE_REVERB_SCHEME=httpWSサーバ起動と疎通確認
ローカルではWSサーバ起動→ブラウザ購読→イベント送信の順に確認します。
# 例:WSサーバ起動(コマンドは環境のReverb導入方法に合わせる)
php artisan reverb:start
# 例:キューワーカー(配信をキューに流す場合)
php artisan queue:workイベント設計:ShouldBroadcastで配信する
“何が起きたか”をイベントに閉じ込め、payload(配信内容)とchannel(配信先)を固定します。
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderStatusUpdated implements ShouldBroadcast
{
use Dispatchable, SerializesModels;
public function __construct(
public int $orderId,
public string $status,
public int $updatedByUserId
) {}
public function broadcastOn(): Channel
{
// 注文IDごとのプライベートチャンネル
return new PrivateChannel("orders.{$this->orderId}");
}
public function broadcastAs(): string
{
return 'order.status.updated';
}
public function broadcastWith(): array
{
return [
'order_id' => $this->orderId,
'status' => $this->status,
'updated_by' => $this->updatedByUserId,
];
}
}エラーの発生条件:broadcastOnがPublicなのに秘匿情報を載せる
PublicChannelは誰でも購読できる前提で運用されます。ユーザーIDや内部ステータス、金額などを載せたpayloadをPublicで飛ばすと情報漏えいになります。
対策:Private/Presenceに寄せ、channel認可で購読者を限定し、payloadを最小化します。
チャンネル認可:routes/channels.php
PrivateChannel/PresenceChannelはここで購読可否を決めます。
use App\Models\Order;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
$order = Order::find($orderId);
if (!$order) return false;
// 例:注文の所有者 or 管理者のみ購読OK
return $order->user_id === $user->id || $user->can('manage-orders');
});送信タイミング:更新処理の直後にdispatch
ドメイン更新 → 監査ログ → WS配信 の順で一貫させると追跡しやすくなります。
use App\Events\OrderStatusUpdated;
DB::transaction(function () use ($order, $newStatus) {
$order->update(['status' => $newStatus]);
event(new OrderStatusUpdated(
orderId: $order->id,
status: $order->status,
updatedByUserId: auth()->id()
));
});フロント購読:Laravel Echo相当で受信する
Echo互換の購読クライアント(Vite+JS)でPrivateChannelを購読し、イベント名でハンドリングします。
import Echo from 'laravel-echo';
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: Number(import.meta.env.VITE_REVERB_PORT),
forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
enabledTransports: ['ws', 'wss'],
});
window.Echo.private(`orders.${orderId}`)
.listen('.order.status.updated', (e) => {
// e: { order_id, status, updated_by }
renderStatus(e.status);
});Presenceで“誰が見ているか”を出す
共同編集や管理画面で「閲覧中ユーザー一覧」を出す場合にPresenceが便利です。
# サーバ側(例)
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('room.{roomId}', function ($user, $roomId) {
return ['id' => $user->id, 'name' => $user->name];
});
# クライアント側(例)
window.Echo.join(`room.${roomId}`)
.here((users) => renderUsers(users))
.joining((user) => addUser(user))
.leaving((user) => removeUser(user));キュー運用:配信を同期にしない
配信を同期処理にすると、WS接続状況や瞬間負荷でHTTPレスポンスが遅くなります。
運用方針:イベント配信はキューに寄せ、ワーカー数とRedisの監視を前提にします。
本番デプロイ:リバースプロキシとWSS
・TLS終端(Nginx/ALB等)でWSSを必須にする
・WebSocketのUpgradeヘッダを通す
・スケール時はWSサーバのセッション保持方式と水平分散(Sticky/共有PubSub)を検討
・CORS/CSRF、認証クッキーのSameSite設定を合わせる
障害対応:よくある詰まりポイント
・購読はできるがイベントが来ない:QUEUE未起動、broadcast driver不一致、イベントがShouldBroadcast未実装
・403で購読できない:channels.phpの認可NG、認証ガード不一致、CSRF/セッション設定
・ローカルはOKで本番NG:WSS未対応、プロキシがUpgradeを遮断、ポート閉鎖
まとめ:Reverb+Broadcastingで“Laravelだけ”に寄せる
・イベントにpayloadと配信先を閉じ込める
・Private/Presenceで認可し、Publicに秘匿を載せない
・配信はキュー前提で遅延・再試行・監視を組み込む
・本番はWSSとプロキシ設定が最重要
-
前の記事
LaravelのPolymorphicリレーションシップを活用する実践パターン 2026.02.20
-
次の記事
記事がありません
コメントを書く