Broadcasting di Laravel/Node — Realtime

Broadcasting Framework-level broadcasting: fire event di backend, auto-delivered ke frontend via WebSocket. Laravel dan Node.js masing-masing punya…

Broadcasting di Laravel mengubah event di server (misalnya OrderStatusChanged) menjadi pesan di sebuah channel WebSocket, yang langsung diterima frontend yang mendengarkan channel itu. Bayangin Laravel sebagai kantor laundry dan server WebSocket sebagai pengeras suara yang tersambung ke HP pelanggan. Ada tiga jenis saluran: umum (siapa pun boleh dengar), privat (harus menunjukkan KTP di loket dulu, yaitu routes/channels.php), dan presence (privat, plus daftar siapa yang sedang hadir). Pengeras suaranya bisa buatan Laravel sendiri (Reverb), bisa juga server Node milikmu yang disuapi lewat Redis.

🧪 Tebakan cepat: OrderStatusChanged meng-implement ShouldBroadcast. Mas Eko menandai #12 Siap diambil, controller memanggil OrderStatusChanged::dispatch($order). Di laptop, QUEUE_CONNECTION=database (bawaan Laravel 13) dan belum ada php artisan queue:work yang jalan. Kapan HP Pak Darto menerima kabarnya? Jawabannya ada di bagian "Event yang di-broadcast".

Event yang di-broadcast

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderStatusChanged implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('orders.'.$this->order->id)];
    }

    public function broadcastWith(): array
    {
        return ['id' => $this->order->id, 'status' => $this->order->status];
    }
}
// controller yang dipanggil tablet Mas Eko
$order->update(['status' => 'Siap diambil']);
OrderStatusChanged::dispatch($order);

Siapa boleh mendengar: routes/channels.php

use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('orders.{order}', function (User $user, Order $order) {
    return $user->id === $order->user_id;
});

Broadcast::channel('papan', function (User $user) {
    return $user->is_staff ? ['id' => $user->id, 'name' => $user->name] : null;
});

Reverb: server WebSocket dari Laravel

php artisan install:broadcasting --reverb memasang laravel/reverb (1.12.0, dicek 11 Oktober 2026), laravel-echo (2.6.1) dan pusher-js (8.6.0), lalu mengisi .env. Reverb memakai protokol Pusher, jadi Echo bicara dengannya lewat pusher-js. Driver lain dengan protokol yang sama: Pusher (layanan berbayar), Ably, atau server seperti Soketi.

php artisan reverb:start          # mendengarkan di 0.0.0.0:8080
php artisan queue:work            # menjalankan job BroadcastEvent

Echo di frontend

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'],
})

Echo.private('orders.12').listen('OrderStatusChanged', (e) => {
  console.log(e.status) // Siap diambil
})

Atau: server Node sendiri, disuapi Redis

Kalau server realtime-mu Node (Socket.IO di localhost:3000), Laravel bisa mem-publish ke Redis: BROADCAST_CONNECTION=redis. Dicoba dengan RedisBroadcaster dari Laravel 13.35.0 dan APP_NAME=Laundry, pesan yang muncul di Redis:

channel: laundry-database-private-orders.12
pesan:   {"event":"App\\Events\\OrderStatusChanged","data":{"id":12,"status":"Siap diambil","socket":null},"socket":null}

Nama channel diawali prefix Redis Laravel (bawaannya Str::slug(APP_NAME) ditambah -database-). Server Node mendengarkan polanya, lalu meneruskan ke room Socket.IO:

const sub = createClient({ url: 'redis://localhost:6379' })
await sub.connect()
await sub.pSubscribe('laundry-database-private-orders.*', (pesan, channel) => {
  const { data } = JSON.parse(pesan)
  const id = channel.split('.').pop()                    // "12"
  io.to(`order:${id}`).emit('order:status', { id: data.id, status: data.status })
})

Di jalur ini routes/channels.php gak dipakai: yang memutuskan siapa boleh masuk order:12 adalah server Node (tiket dan pemeriksaan join dari pelajaran sebelumnya), dan frontend memakai socket.io-client, bukan Echo dengan Reverb.

Dari tablet ke HP: OrderStatusChanged ujung ke ujung

# Di mana Yang terjadi
1 HP Pak Darto (sejak buka) Echo.private('orders.12'), POST /broadcasting/auth dijawab 200
2 Tablet Mas Eko menandai #12 Siap diambil, request ke Laravel
3 Laravel $order->update(...), OrderStatusChanged::dispatch($order), job masuk queue
4 queue:work menjalankan job: nama App\Events\OrderStatusChanged, channel private-orders.12, data {"id":12,"status":"Siap diambil"}
5 Reverb menerima event dari Laravel lewat HTTP, mengirimkannya ke semua koneksi di private-orders.12
6 HP Pak Darto listener OrderStatusChanged jalan, e.status = Siap diambil

⚠️ Jebakan yang sering ketemu

TL;DR: Event yang meng-implement ShouldBroadcast dikirim lewat queue (perlu php artisan queue:work; ShouldBroadcastNow langsung), ke channel dari broadcastOn() (PrivateChannel('orders.12') jadi private-orders.12), dengan isi dari broadcastWith() dan nama App\Events\OrderStatusChanged. routes/channels.php memutuskan siapa boleh berlangganan channel privat dan presence (array data user) lewat POST /broadcasting/auth. Reverb (1.12.0) adalah server WebSocket berprotokol Pusher dari Laravel; Echo (2.6.1) mendengarkan dengan Echo.private('orders.12').listen('OrderStatusChanged', ...). Dengan BROADCAST_CONNECTION=redis, Laravel mem-publish ke Redis (channel ber-prefix laundry-database-), dan server Node-mu meneruskannya ke room Socket.IO, dengan otorisasi di Node.

Yang akan kamu pelajari