Real-Time Applications with Laravel Reverb
For years, Laravel developers relied on Pusher (expensive) or Laravel Echo Server / Soketi (Node.js required) for WebSockets.
Laravel 11 introduced Reverb, a WebSocket server written directly in PHP (using Event Loop). It allows you to build real-time apps without leaving the PHP ecosystem.
Why Reverb?
- Native PHP: No Node.js dependency
- Zero cost: Self-hosted, no per-message pricing
- First-party: Maintained by the Laravel team
- Fast: Built on ReactPHP, handles thousands of connections
- Simple: Works with existing Laravel Broadcasting
Installation
php artisan install:broadcasting
This command asks if you want to install Reverb. Say "Yes".
It installs the package, publishes configuration, and configures your environment.
Manual Installation
composer require laravel/reverb
php artisan reverb:install
Configure your .env:
BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
How it Works
- Backend Event: You fire an event in PHP.
MessageSent::dispatch($message); - Broadcasting: If the event implements
ShouldBroadcast, Laravel pushes it to the Reverb server (via Redis or direct connection). - Client (Laravel Echo): The frontend is listening to a channel and receives the JSON payload instantly.
The Event
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class MessageSent implements ShouldBroadcast
{
public function __construct(
public Message $message
) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('chat.' . $this->message->room_id),
];
}
// Customize the payload
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'content' => $this->message->content,
'user' => $this->message->user->only(['id', 'name', 'avatar']),
'created_at' => $this->message->created_at->toISOString(),
];
}
}
Channel Authorization
For private channels, define authorization in routes/channels.php:
use App\Models\Room;
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
return Room::find($roomId)?->users->contains($user);
});
The Frontend (Laravel Echo)
Install the required packages:
npm install laravel-echo pusher-js
Configure Echo in resources/js/bootstrap.js:
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,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Listen for events in your component:
Echo.private(`chat.${roomId}`)
.listen('MessageSent', (e) => {
console.log('New message:', e);
messages.value.push(e);
})
.listenForWhisper('typing', (e) => {
console.log(`${e.name} is typing...`);
});
Presence Channels
Track who's online with presence channels:
// routes/channels.php
Broadcast::channel('room.{roomId}', function ($user, $roomId) {
if (Room::find($roomId)?->users->contains($user)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
Echo.join(`room.${roomId}`)
.here((users) => {
onlineUsers.value = users;
})
.joining((user) => {
onlineUsers.value.push(user);
})
.leaving((user) => {
onlineUsers.value = onlineUsers.value.filter(u => u.id !== user.id);
});
Client-Side Events (Whispers)
Send events directly from client to client (e.g., "typing" indicators):
// Send
Echo.private(`chat.${roomId}`).whisper('typing', {
name: currentUser.name
});
// Receive
.listenForWhisper('typing', (e) => {
typingUser.value = e.name;
});
Running Reverb in Production
Start the Reverb server:
php artisan reverb:start
For production, use Supervisor to keep it running:
[program:reverb]
command=php /var/www/html/artisan reverb:start
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/reverb.log
Scaling with Redis
For horizontal scaling, Reverb uses Redis Pub/Sub to synchronize multiple servers:
REVERB_SCALING_ENABLED=true
REDIS_HOST=your-redis-host
This allows you to run multiple Reverb instances behind a load balancer.
Use Cases
- Chat applications
- Notification centers
- Live dashboard metrics
- "User is typing..." indicators
- Real-time collaboration (Google Docs style)
- Live sports scores
- Multiplayer game implementations
Debugging
Enable debug mode to see connection logs:
php artisan reverb:start --debug
Real-time is no longer "hard" in PHP. With Reverb, it's native, performant, and cost-effective.