Real-Time Applications with Laravel Reverb

· 4 min read

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

  1. Backend Event: You fire an event in PHP.
    MessageSent::dispatch($message);
    
  2. Broadcasting: If the event implements ShouldBroadcast, Laravel pushes it to the Reverb server (via Redis or direct connection).
  3. 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.

Comments