I am a full-stack developer, and I make video tutorials on my youtube channel, Qirolab.
The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.
このチュートリアルでは、Laravel、Nuxt 3、Sanctum、Laravel Reverb を使用して、ユーザー間の安全でライブなメッセージングを処理するリアルタイム チャット アプリケーションを構築します。ユーザー認証を設定し、安全な API に接続し、チャットが即座に更新されてスムーズで応答性の高いエクスペリエンスが実現されるようにします。
シングルページ アプリケーション (SPA) と API 認証の両方を効率的に処理する SPA 認証を管理するモジュールを使用します。このモジュールの使用の詳細については、 Nuxt 3 SPA 認証に関する記事を参照してください。
このプロジェクトでは、リアルタイム イベント ブロードキャスト用に Laravel Reverb を設定し、Sanctum で認証を実装し、チャット メッセージを動的に表示および管理する Nuxt 3 フロントエンドを構築します。さあ、始めましょう!
まず、Laravel Sanctum がインストールされ、設定されていることを確認します。Sanctum を使用すると、シングルページ アプリケーション (SPA) のトークンベースの認証が可能になります。次に、リアルタイム機能を利用するために Laravel Reverb をインストールして設定します。
次の Reverb 環境変数を.env
ファイルに追加します。
REVERB_APP_ID=my-app-id REVERB_APP_KEY=my-app-key REVERB_APP_SECRET=my-app-secret REVERB_HOST="localhost" REVERB_PORT=8080 REVERB_SCHEME=http
チャット メッセージを保存するには、 chat_messages
テーブルの移行を作成します。以下を実行します。
php artisan make:migration create_chat_messages_table
移行ファイルを次のように更新します。
Schema::create('chat_messages', function (Blueprint $table) { $table->id(); $table->foreignId('receiver_id'); $table->foreignId('sender_id'); $table->text('text'); $table->timestamps(); });
移行を実行してテーブルを作成します。
php artisan migrate
メッセージをリアルタイムでブロードキャストするには、 MessageSent
イベント クラスを作成します。
php artisan make:event MessageSent
イベント クラスを更新します。
<?php namespace App\Events; use App\Models\ChatMessage; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageSent implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct(public ChatMessage $message) { // } public function broadcastOn(): array { return [new PrivateChannel("chat.{$this->message->receiver_id}")]; } }
channels.php
で、ブロードキャスト チャネルを定義します。
Broadcast::channel('chat.{id}', function ($user, $id) { return (int) $user->id === (int) $id; });
チャット メッセージの送信と取得を管理するには、次のルートを追加します。
Route::get('/messages/{user}', function (User $user, Request $request) { return ChatMessage::query() ->where(function ($query) use ($user, $request) { $query->where('sender_id', $request->user()->id) ->where('receiver_id', $user->id); }) ->orWhere(function ($query) use ($user, $request) { $query->where('sender_id', $user->id) ->where('receiver_id', $request->user()->id); }) ->with(['sender', 'receiver']) ->orderBy('id', 'asc') ->get(); })->middleware('auth:sanctum'); Route::post('/messages/{user}', function (User $user, Request $request) { $request->validate(['message' => 'required|string']); $message = ChatMessage::create([ 'sender_id' => $request->user()->id, 'receiver_id' => $user->id, 'text' => $request->message ]); broadcast(new MessageSent($message)); return $message; });
Nuxt 3 が Reverb に接続できるようにするには、次の変数を.env
ファイルに追加します。
NUXT_PUBLIC_REVERB_APP_ID=my-app-id NUXT_PUBLIC_REVERB_APP_KEY=my-app-key NUXT_PUBLIC_REVERB_APP_SECRET=my-app-secret NUXT_PUBLIC_REVERB_HOST="localhost" NUXT_PUBLIC_REVERB_PORT=8080 NUXT_PUBLIC_REVERB_SCHEME=http
次に、 nuxt.config.ts
にロードします。
export default defineNuxtConfig({ runtimeConfig: { public: { REVERB_APP_ID: process.env.NUXT_PUBLIC_REVERB_APP_ID, REVERB_APP_KEY: process.env.NUXT_PUBLIC_REVERB_APP_KEY, REVERB_APP_SECRET: process.env.NUXT_PUBLIC_REVERB_APP_SECRET, REVERB_HOST: process.env.NUXT_PUBLIC_REVERB_HOST, REVERB_PORT: process.env.NUXT_PUBLIC_REVERB_PORT, REVERB_SCHEME: process.env.NUXT_PUBLIC_REVERB_SCHEME, }, }, });
リアルタイム更新を管理するには、 laravel-echo.client.ts
プラグインを作成します。
import Echo from "laravel-echo"; import Pusher, { type ChannelAuthorizationCallback } from "pusher-js"; declare global { interface Window { Echo: Echo; Pusher: typeof Pusher; } } export default defineNuxtPlugin(() => { window.Pusher = Pusher; const config = useRuntimeConfig(); const echo = new Echo({ broadcaster: "reverb", key: config.public.REVERB_APP_KEY, wsHost: config.public.REVERB_HOST, wsPort: config.public.REVERB_PORT ?? 80, wssPort: config.public.REVERB_PORT ?? 443, forceTLS: (config.public.REVERB_SCHEME ?? "https") === "https", enabledTransports: ["ws", "wss"], authorizer: (channel, options) => ({ authorize: (socketId, callback) => { useSanctumFetch("api/broadcasting/auth", { method: "post", body: { socket_id: socketId, channel_name: channel.name }, }) .then(response => callback(null, response)) .catch(error => callback(error, null)); }, }), }); return { provide: { echo } }; });
Nuxt 3 アプリでリアルタイム チャットを有効にするために、ユーザーが他のユーザーを選択してチャットできる新しいページ、 chats > [id].vue
を作成しました。このページは、Laravel バックエンドに接続してチャット メッセージとユーザー データを管理し、リアルタイム更新と入力インジケーターに Laravel Echo と WebSocket を使用します。
このチャット機能の構造の詳細は次のとおりです。
まず、 useRoute
コンポーザブルを使用して URL からuserID
取得します。このuserID
チャットしているユーザーを識別し、必要なユーザー メッセージとチャット メッセージを読み込みます。
const route = useRoute(); const userID = route.params.id; const { user: currentUser } = useSanctum<User>(); const { data: user } = await useAsyncData( `user-${userID}`, () => useSanctumFetch<User>(`/api/users/${userID}`) ); const { data: messages } = useAsyncData( `messages-${userID}`, () => useSanctumFetch<ChatMessage[]>(`/api/messages/${userID}`), { default: (): ChatMessage[] => [] } );
このスニペットは、カスタム コンポーザブルであるuseSanctumFetch
使用して、ページがマウントされるときにメッセージを非同期的に読み込みます。
各メッセージを動的にレンダリングし、現在のユーザーからのメッセージかチャット参加者からのメッセージかに基づいてスタイルを設定します。
<div ref="messagesContainer" class="p-4 overflow-y-auto max-h-fit"> <div v-for="message in messages" :key="message.id" class="flex items-center mb-2"> <div v-if="message.sender_id === currentUser.id" class="p-2 ml-auto text-white bg-blue-500 rounded-lg"> {{ message.text }} </div> <div v-else class="p-2 mr-auto bg-gray-200 rounded-lg"> {{ message.text }} </div> </div> </div>
チャット ウィンドウはnextTick()
を使用して最新のメッセージまで自動的にスクロールします。
watch( messages, () => { nextTick(() => messageContainerScrollToBottom()); }, { deep: true } ); function messageContainerScrollToBottom() { if (!messagesContainer.value) return; messagesContainer.value.scrollTo({ top: messagesContainer.value.scrollHeight, behavior: 'smooth' }); }
ユーザーは入力フィールドを使用してメッセージを送信できます。送信すると、POST リクエストが Laravel バックエンドに送信されます。
const newMessage = ref(""); const sendMessage = async () => { if (!newMessage.value.trim()) return; const messageResponse = await useSanctumFetch<ChatMessage>(`/api/messages/${userID}`, { method: "post", body: { message: newMessage.value } }); messages.value.push(messageResponse); newMessage.value = ""; };
メッセージは送信後すぐに更新されます。
Laravel Echo は、 MessageSent
やtyping
などの主要なイベントをリッスンします。
onMounted(() => { if (currentUser.value) { $echo.private(`chat.${currentUser.value.id}`) .listen('MessageSent', (response: { message: ChatMessage }) => { messages.value.push(response.message); }) .listenForWhisper("typing", (response: { userID: number }) => { isUserTyping.value = response.userID === user.value?.id; if (isUserTypingTimer.value) clearTimeout(isUserTypingTimer.value); isUserTypingTimer.value = setTimeout(() => { isUserTyping.value = false; }, 1000); }); } });
これはリアルタイムのメッセージ更新と入力インジケーターを処理します。これらは 1 秒間操作しないと消えます。
入力インジケーターを表示するには、 whisper
を使用して「入力」イベントをトリガーします。
const sendTypingEvent = () => { if (!user.value || !currentUser.value) return; $echo.private(`chat.${user.value.id}`).whisper("typing", { userID: currentUser.value.id }); };
このイベントはユーザーが入力するたびに送信され、受信者には入力インジケーターが表示されます。
チャット インターフェイスには、選択したユーザーの名前を含むヘッダーと、メッセージのセクションと入力フィールドが含まれます。
<template> <div> <header v-if="user" class="bg-white shadow"> <div class="px-4 py-6 mx-auto max-w-7xl sm:px-6 lg:px-8"> <h1 class="text-2xl font-bold ">{{ user.name }}</h1> </div> </header> <div class="flex flex-col items-center py-5"> <div class="container w-full h-full p-8 space-y-3 bg-white rounded-xl"> <div v-if="currentUser"> <div class="flex flex-col justify-end h-80"> <div ref="messagesContainer" class="p-4 overflow-y-auto max-h-fit"> <div v-for="message in messages" :key="message.id" class="flex items-center mb-2"> <div v-if="message.sender_id === currentUser.id" class="p-2 ml-auto text-white bg-blue-500 rounded-lg"> {{ message.text }} </div> <div v-else class="p-2 mr-auto bg-gray-200 rounded-lg"> {{ message.text }} </div> </div> </div> </div> <div class="flex-shrink-0"> <span v-if="user && isUserTyping" class="text-gray-500"> {{ user.name }} is typing... </span> <div class="flex items-center justify-between w-full p-4 border-t border-gray-200"> <input type="text" v-model="newMessage" @keydown="sendTypingEvent" @keyup.enter="sendMessage" class="w-full p-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Type a message..." /> <button @click.prevent="sendMessage" class="inline-flex items-center justify-center w-12 h-12 ml-4 text-white bg-blue-500 rounded-lg hover:bg-blue-600"> <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> </svg> </button> </div> </div> </div> </div> </div> </div> </template>
これで、Nuxt 3 で Laravel Sanctum と Echo を使用したリアルタイム チャット機能のセットアップが完了します。
これで、Laravel、Sanctum、Reverb、Nuxt 3 を使用して、安全なリアルタイム チャット アプリケーションが作成されました。このセットアップは、メッセージ反応や複数のチャットルームなどの追加機能を含めるように簡単に拡張できます。
完全なコードについては、 GitHub リポジトリをご覧ください。