Hello, laravel web developers! In this article, we'll see how to create real-time chat in laravel 11 reverb with vue.js. Here, we'll create a real-time chat application in laravel 11. Reverb is a first-party WebSocket server for Laravel applications, bringing real-time communication between client and server directly to your fingertips
It's two-way, real-time communication based on WebSockets technology that allows web pages to receive updates on the server without a complete page refresh.
Laravel 11 Real Time Chat using Reverb and Vue
In this step, we'll install the laravel 11 application using the following command.
composer create-project laravel/laravel laravel_11_live_chat_example
Next, we'll install Laravel Reverb by running the following command.
php artisan install:broadcasting
This command will run the reverb:install
command, which will install Reverb with a sensible set of default configuration options. you can modify its configuration from the config/reverb.php
file.
Connection to Reverb, a set of Reverb "application" credentials must be exchanged between the client and server. These credentials are configured on the server and are used to verify the request from the client.
You can define these credentials using the following environment variables.
BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
It will also creates echo.js
in the resources/js
directory.
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Now, run the reverb server using the following command.
php artisan reverb:start
If you want to set a specific host or port, you can use the –host
and –port
options when starting the server.
php artisan reverb:start --host=127.0.0.1 --port=8001
Then, we'll configure a database and add the host, database name, etc.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_11
DB_USERNAME=root
DB_PASSWORD=root
Next, we'll create a Model and migration using the following command.
php artisan make:model ChatMessage --migration
Migration:
Schema::create('chat_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('receiver_id');
$table->foreignId('sender_id');
$table->text('text');
$table->timestamps();
});
Next, migrate the table into the database using the following command.
php artisan migrate
Now, update the ChatMessage.php Model.
app/Models/ChatMessage.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ChatMessage extends Model
{
use HasFactory;
protected $fillable = [
'sender_id',
'receiver_id',
'text'
];
public function sender()
{
return $this->belongsTo(User::class, 'sender_id');
}
public function receiver()
{
return $this->belongsTo(User::class, 'receiver_id');
}
}
Next, we'll create an event MessageSent to handle the broadcasting of messages. This event will implement Laravel's ShouldBroadcastNow
interface, which allows for immediate broadcasting over WebSockets without queuing.
app/Events/MessageSent.php
<?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;
use InteractsWithSockets;
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public ChatMessage $message)
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel("chat.{$this->message->receiver_id}"),
];
}
}
Then, we'll create a private channel for real-time communication.
channels.php
<?php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('chat.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Next, we'll define the routes for the chat room, get a chat message, and send a chat message.
// Chat Room Route
Route::get('/chat/{friend}', function (User $friend) {
return view('chat', [
'friend' => $friend
]);
})->middleware(['auth'])->name('chat');
// Get Chat Messages Route
Route::get('/messages/{friend}', function (User $friend) {
return ChatMessage::query()
->where(function ($query) use ($friend) {
$query->where('sender_id', auth()->id())
->where('receiver_id', $friend->id);
})
->orWhere(function ($query) use ($friend) {
$query->where('sender_id', $friend->id)
->where('receiver_id', auth()->id());
})
->with(['sender', 'receiver'])
->orderBy('id', 'asc')
->get();
})->middleware(['auth']);
// Send Chat Message Route
Route::post('/messages/{friend}', function (User $friend) {
$message = ChatMessage::create([
'sender_id' => auth()->id(),
'receiver_id' => $friend->id,
'text' => request()->input('message')
]);
broadcast(new MessageSent($message));
return $message;
});
Then, we'll create a new file chat.blade.php in the resources/views directory and add the following code.
resources/views/chat.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ $friend->name }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<chat-component
:friend="{{ $friend }}"
:current-user="{{ auth()->user() }}"
/>
</div>
</div>
</div>
</div>
</x-app-layout>
Then, we'll create a chat component and add the following code.
<template>
<div>
<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 items-center">
<input
type="text"
v-model="newMessage"
@keydown="sendTypingEvent"
@keyup.enter="sendMessage"
placeholder="Type a message..."
class="flex-1 px-2 py-1 border rounded-lg"
/>
<button
@click="sendMessage"
class="px-4 py-1 ml-2 text-white bg-blue-500 rounded-lg"
>
Send
</button>
</div>
<small v-if="isFriendTyping" class="text-gray-700">
{{ friend.name }} is typing...
</small>
</div>
</template>
<script setup>
import axios from "axios";
import { nextTick, onMounted, ref, watch } from "vue";
const props = defineProps({
friend: {
type: Object,
required: true,
},
currentUser: {
type: Object,
required: true,
},
});
const messages = ref([]);
const newMessage = ref("");
const messagesContainer = ref(null);
const isFriendTyping = ref(false);
const isFriendTypingTimer = ref(null);
watch(
messages,
() => {
nextTick(() => {
messagesContainer.value.scrollTo({
top: messagesContainer.value.scrollHeight,
behavior: "smooth",
});
});
},
{ deep: true }
);
const sendMessage = () => {
if (newMessage.value.trim() !== "") {
axios
.post(`/messages/${props.friend.id}`, {
message: newMessage.value,
})
.then((response) => {
messages.value.push(response.data);
newMessage.value = "";
});
}
};
const sendTypingEvent = () => {
Echo.private(`chat.${props.friend.id}`).whisper("typing", {
userID: props.currentUser.id,
});
};
onMounted(() => {
axios.get(`/messages/${props.friend.id}`).then((response) => {
console.log(response.data);
messages.value = response.data;
});
Echo.private(`chat.${props.currentUser.id}`)
.listen("MessageSent", (response) => {
messages.value.push(response.message);
})
.listenForWhisper("typing", (response) => {
isFriendTyping.value = response.userID === props.friend.id;
if (isFriendTypingTimer.value) {
clearTimeout(isFriendTypingTimer.value);
}
isFriendTypingTimer.value = setTimeout(() => {
isFriendTyping.value = false;
}, 1000);
});
});
</script>
Now, run the laravel 11 application using the following command.
php artisan serve
Then, run the following command.
npm run dev
Run laravel reverb using the following command.
php artisan reverb:start
You might also like: