Your resource for web content, online publishing
and the distribution of digital products.
S M T W T F S
 
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
 
24
 
25
 
26
 
27
 
28
 
29
 
30
 
31
 
 

Here Are the Frameworks You Need to Build a Real-Time Chat App

DATE POSTED:January 21, 2025

In this tutorial, we will build a real-time chat application using Laravel, Nuxt 3, Sanctum, and Laravel Reverb to handle secure and live messaging between users. We will set up user authentication, connect to a secure API, and ensure that the chat updates instantly for a smooth and responsive experience.

\ We will use the module to manage SPA authentication, which efficiently handles both Single-Page Application (SPA) and API authentication. To learn more about using this module, see the article on Nuxt 3 SPA authentication.

\ In this project, we’ll configure Laravel Reverb for real-time event broadcasting, implement authentication with Sanctum, and build a Nuxt 3 frontend that dynamically displays and manages chat messages. Let’s get started!

https://www.youtube.com/watch?v=bjcM8E5s8p0&embedable=true

Prerequisites
  • Basic familiarity with Laravel, Sanctum, and Nuxt 3.
  • Understanding of event broadcasting in Laravel.
  • A Laravel project set up with Sanctum.
  • Nuxt 3 is installed and configured as your app’s frontend.
Step 1: Set Up Laravel Sanctum and Laravel Reverb

First, ensure Laravel Sanctum is installed and configured. Sanctum allows token-based authentication for single-page applications (SPA). Then, install and configure Laravel Reverb for real-time capabilities.

\ Add the following Reverb environment variables to your .env file:

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 Step 2: Create Chat Messages Table Migration

To store chat messages, create a migration for a chat_messages table. Run:

php artisan make:migration create_chat_messages_table

\ Update the migration file as follows:

Schema::create('chat_messages', function (Blueprint $table) { $table->id(); $table->foreignId('receiver_id'); $table->foreignId('sender_id'); $table->text('text'); $table->timestamps(); });

\ Run the migration to create the table:

php artisan migrate Step 3: Create the MessageSent Event

For broadcasting messages in real-time, create a MessageSent event class:

php artisan make:event MessageSent

\ Update the event class:

message->receiver_id}")]; } } Step 4: Define Broadcast Channels

In channels.php, define the broadcast channel:

Broadcast::channel('chat.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); Step 5: Define Routes for Fetching and Sending Messages

Add these routes to manage sending and retrieving chat messages:

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; }); Step 6: Configure Reverb in Nuxt 3

To allow Nuxt 3 to connect with Reverb, add these variables to your .env file:

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

\ Then, load them in 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, }, }, }); Step 7: Set Up Laravel Echo Client in Nuxt 3

To manage real-time updates, create a laravel-echo.client.ts plugin:

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 } }; }); Step 8: Build the Real-Time Chat Interface in Nuxt 3

To enable real-time chat in our Nuxt 3 app, I created a new page, chats > [id].vue, where users can select and chat with other users. This page connects to a Laravel backend to manage chat messages and user data, using Laravel Echo and WebSockets for real-time updates and typing indicators.

\ Here’s a breakdown of how we structured this chat functionality:

Step 8.1: Load Selected User's Chat Data

First, we retrieve the userID from the URL with the useRoute composable. This userID identifies the user we're chatting with and loads the necessary user and chat messages.

const route = useRoute(); const userID = route.params.id; const { user: currentUser } = useSanctum(); const { data: user } = await useAsyncData( `user-${userID}`, () => useSanctumFetch(`/api/users/${userID}`) ); const { data: messages } = useAsyncData( `messages-${userID}`, () => useSanctumFetch(`/api/messages/${userID}`), { default: (): ChatMessage[] => [] } );

This snippet uses useSanctumFetch, a custom composable, to load messages asynchronously when the page mounts.

Step 8.2: Displaying Chat Messages

We render each message dynamically, styling them based on whether they are from the current user or the chat participant.

{{ message.text }}
{{ message.text }}

The chat window automatically scrolls to the latest message using nextTick().

watch( messages, () => { nextTick(() => messageContainerScrollToBottom()); }, { deep: true } ); function messageContainerScrollToBottom() { if (!messagesContainer.value) return; messagesContainer.value.scrollTo({ top: messagesContainer.value.scrollHeight, behavior: 'smooth' }); } Step 8.3: Sending New Messages

Users can send messages with an input field. Upon submission, a POST request is sent to the Laravel backend.

const newMessage = ref(""); const sendMessage = async () => { if (!newMessage.value.trim()) return; const messageResponse = await useSanctumFetch(`/api/messages/${userID}`, { method: "post", body: { message: newMessage.value } }); messages.value.push(messageResponse); newMessage.value = ""; };

Messages are updated immediately after being sent.

Step 8.4: Real-Time Features with Laravel Echo

Laravel Echo listens for key events like MessageSent and 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); }); } });

This handles real-time message updates and typing indicators, which disappear after a second of inactivity.

Step 8.5: Typing Indicator

To show a typing indicator, we trigger a "typing" event with whisper.

const sendTypingEvent = () => { if (!user.value || !currentUser.value) return; $echo.private(`chat.${user.value.id}`).whisper("typing", { userID: currentUser.value.id }); };

This event is sent whenever the user types, and the recipient sees the typing indicator.

Step 8.6: User Interface

The chat interface includes a header with the selected user's name and sections for messages and the input field.

This completes the real-time chat feature setup using Laravel Sanctum and Echo with Nuxt 3.

Conclusion

We’ve now created a secure, real-time chat application using Laravel, Sanctum, Reverb, and Nuxt 3. This setup can be easily scaled to include additional features like message reactions or multiple chatrooms.

\ For the full code, visit the GitHub repository.