Laravel Reverb is changing the game for real-time applications. If you’re looking to build a live chat system without relying on third-party services like Pusher, you’re in the right place.
I’ll guide you through building a real-time public chat app step by step using:
- Laravel Reverb (WebSocket broadcasting)
- Laravel Breeze (Blade + Alpine.js)
- Tailwind CSS
- Laravel Echo on the frontend
You’ll also learn how to avoid common pitfalls, debug broadcast issues, and get this thing working in a production-friendly way.
đź§Ş Prefer Cloning? Use This Repository
If you’d like to skip the setup and just see how it’s done, feel free to clone the full project from GitHub:
đź”— GitHub Repository
👉 https://github.com/Tahsin000/reverb-live-chat
git clone https://github.com/Tahsin000/reverb-live-chat.git
cd reverb-live-chat
composer install
npm install && npm run dev
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan reverb:start
php artisan serve
Now open http://localhost:8000/chat — and you’re in!
🎯 What You’ll Build
A public real-time chat app where:
- Users send/receive messages instantly
- Everything updates live with WebSocket (Reverb)
- The UI is clean and responsive with Alpine.js + Tailwind
🔧 Manual Setup (If You’re Building It Yourself)
Step 1: Create New Project & Install Breeze
laravel new reverb-live-chat
cd reverb-live-chat
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrate
php artisan serve
Step 2: Install and Start Laravel Reverb
php artisan install:reverb
php artisan reverb:start
Update .env
:
BROADCAST_DRIVER=reverb
Step 3: Create Message Model + Migration
php artisan make:model Message -m
Update migration:
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('content');
$table->string('room')->default('public');
Run:
php artisan migrate
In Message.php
:
protected $fillable = ['user_id', 'content', 'room'];
public function user() {
return $this->belongsTo(User::class);
}
Step 4: Create and Broadcast Event
php artisan make:event MessageSent
In MessageSent.php
:
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public Message $message) {}
public function broadcastOn(): Channel
{
return new Channel('chat.public');
}
public function broadcastWith(): array
{
return [
'message' => $this->message->load('user'),
];
}
}
Add logging if needed:
Log::info('MessageSent Event fired', ['message' => $this->message]);
Step 5: ChatController
php artisan make:controller ChatController
Add methods:
public function fetch($room = 'public') {
return Message::with('user')->where('room', $room)->latest()->get();
}
public function send(Request $request) {
$message = Message::create([
'user_id' => auth()->id(),
'content' => $request->content,
'room' => $request->room ?? 'public',
]);
broadcast(new MessageSent($message))->toOthers();
return response()->json(['status' => 'Message Sent!']);
}
Step 6: Routes
Route::middleware('auth')->group(function () {
Route::view('/chat', 'chat');
Route::get('/chat/messages', [ChatController::class, 'fetch']);
Route::post('/chat/send', [ChatController::class, 'send']);
});
Step 7: chat.blade.php
@extends('layouts.app')
@section('content')
<div class="container mx-auto mt-10" x-data="chatApp()" x-init="init()">
<h2 class="text-xl font-bold mb-4">Live Chat</h2>
<div class="border p-4 h-96 overflow-y-scroll mb-4 bg-gray-100 rounded">
<template x-for="msg in messages" :key="msg.id">
<div class="mb-2">
<strong x-text="msg.user.name"></strong>:
<span x-text="msg.content"></span>
</div>
</template>
</div>
<form @submit.prevent="sendMessage" class="flex gap-2">
<input type="text" x-model="newMessage" class="flex-1 border p-2 rounded" placeholder="Type your message..." required>
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">Send</button>
</form>
</div>
@endsection
@push('scripts')
<script src="//unpkg.com/alpinejs" defer></script>
<script type="module">
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'local',
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
});
function chatApp() {
return {
messages: [],
newMessage: '',
user: @json(auth()->user()),
init() {
this.fetchMessages();
Echo.channel('chat.public')
.listen('MessageSent', (e) => {
this.messages.unshift(e.message);
});
},
fetchMessages() {
fetch('/chat/messages')
.then(res => res.json())
.then(data => this.messages = data);
},
sendMessage() {
fetch('/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ content: this.newMessage })
}).then(() => this.newMessage = '');
}
}
}
</script>
@endpush
đź§ Common Issues and Fixes
Issue | Solution |
---|---|
Uncaught ReferenceError: require is not defined |
Use import instead of require() when working with Vite |
Event not firing | Check if you ran php artisan queue:work
|
Queue not logging | Add Log::info() inside the event |
$slot undefined |
Switch to @yield('content') in layout |
Messages not showing for others | Make sure to use .toOthers() when broadcasting |
âś… Wrapping Up
You now have a working real-time public chat system in Laravel using native tools — no Pusher needed.
đź› Want to learn more?
Let me know if you want:
- Private messaging
- Typing indicators
- Group chat rooms
📌 GitHub Repo Again
đź”— Clone it now
👉 https://github.com/Tahsin000/reverb-live-chat