Fitur real-time seperti notifikasi instan, chat, atau dashboard yang update otomatis dulu membutuhkan layanan pihak ketiga berbayar seperti Pusher atau Ably. Sejak Laravel 11, hadir Laravel Reverb — WebSocket server yang ditulis dalam PHP, dibangun langsung ke ekosistem Laravel, dan bisa self-hosted gratis. Artikel ini membahas cara implementasinya dari awal.

Arsitektur Real-time di Laravel

Sebelum masuk ke implementasi, penting memahami alur data real-time di Laravel:

  1. Event di-dispatch dari aplikasi Laravel (controller, job, model)
  2. Event di-broadcast ke channel WebSocket via Reverb
  3. Frontend (JavaScript) listen ke channel dan update UI

Instalasi Laravel Reverb

php artisan install:broadcasting

# Ini akan menginstall:
# - laravel/reverb
# - laravel-echo
# - pusher-js (digunakan oleh Echo sebagai WebSocket client)
# .env
BROADCAST_CONNECTION=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
REVERB_SCHEME=http

# Untuk frontend
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Membuat Broadcastable Event

php artisan make:event PostPublished
class PostPublished implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public readonly Post $post) {}

    /**
     * Channel yang menerima event ini.
     */
    public function broadcastOn(): array
    {
        return [
            new PublicChannel('posts'),                    // Semua user bisa dengar
            new PrivateChannel("user.{$this->post->author_id}"), // Hanya author
        ];
    }

    /**
     * Data yang dikirim ke frontend.
     */
    public function broadcastWith(): array
    {
        return [
            'id'    => $this->post->id,
            'title' => $this->post->title,
            'slug'  => $this->post->slug,
            'url'   => route('posts.show', $this->post),
        ];
    }

    /**
     * Nama event di frontend (default: nama class).
     */
    public function broadcastAs(): string
    {
        return 'post.published';
    }
}

Channel Authorization untuk Private Channel

// routes/channels.php
Broadcast::channel('user.{id}', function (User $user, int $id) {
    return $user->id === $id;
});

// Channel untuk admin saja
Broadcast::channel('admin.dashboard', function (User $user) {
    return $user->hasRole('admin');
});

Dispatch Event

// Di mana saja di aplikasi — controller, job, observer
PostPublished::dispatch($post);

// Atau dengan delay
PostPublished::dispatch($post)->delay(now()->addSeconds(5));

// Broadcast tanpa queue (langsung)
broadcast(new PostPublished($post));

Frontend: Listen dengan Laravel Echo

// resources/js/bootstrap.ts
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'],
});
// Listen ke public channel
Echo.channel('posts')
    .listen('.post.published', (data) => {
        console.log('Post baru:', data.title);
        showToast(`Post baru: ${data.title}`);
    });

// Listen ke private channel (perlu autentikasi)
Echo.private(`user.${userId}`)
    .listen('.post.published', (data) => {
        addNotification({ message: `Post Anda "${data.title}" dipublikasikan!` });
    });

// Presence channel (tahu siapa yang online)
Echo.join('chat.room.1')
    .here((users) => { onlineUsers.value = users; })
    .joining((user) => { onlineUsers.value.push(user); })
    .leaving((user) => { onlineUsers.value = onlineUsers.value.filter(u => u.id !== user.id); })
    .listen('.message.sent', (data) => { messages.value.push(data); });

Studi Kasus: Notifikasi Real-time

// app/Notifications/NewCommentNotification.php
class NewCommentNotification extends Notification implements ShouldBroadcast
{
    use Queueable;

    public function __construct(private readonly Comment $comment) {}

    public function via(object $notifiable): array
    {
        return ['database', 'broadcast'];
    }

    public function toBroadcast(object $notifiable): BroadcastMessage
    {
        return new BroadcastMessage([
            'id'         => $this->comment->id,
            'message'    => "Komentar baru di: {$this->comment->post->title}",
            'author'     => $this->comment->author->name,
            'created_at' => $this->comment->created_at->diffForHumans(),
        ]);
    }
}

// Kirim notifikasi
$post->author->notify(new NewCommentNotification($comment));

Menjalankan Reverb Server

# Development
php artisan reverb:start

# Production dengan opsi
php artisan reverb:start --host="0.0.0.0" --port=8080 --env=production

# Dengan Supervisor (production)
# /etc/supervisor/conf.d/reverb.conf
[program:reverb]
command=php /var/www/app/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
user=www-data

Performance dan Scaling

Reverb menggunakan ReactPHP untuk event-driven I/O yang efisien. Satu instance bisa menangani ribuan koneksi concurrent. Untuk aplikasi besar, Reverb mendukung horizontal scaling via Redis pub/sub:

# config/reverb.php
'scaling' => [
    'driver' => 'redis',
    'connection' => 'default',
],

Dengan Reverb, fitur real-time yang dulu memerlukan biaya Pusher $49/bulan ke atas kini bisa dijalankan sendiri. Untuk aplikasi dengan traffic sedang hingga tinggi, self-hosting Reverb di VPS yang sama dengan Laravel bisa menghemat biaya signifikan.