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:
OrderStatusChangedmeng-implementShouldBroadcast. Mas Eko menandai #12 Siap diambil, controller memanggilOrderStatusChanged::dispatch($order). Di laptop,QUEUE_CONNECTION=database(bawaan Laravel 13) dan belum adaphp artisan queue:workyang 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);
ShouldBroadcastartinya event ini dikirim lewat queue: Laravel membuat jobBroadcastEventdan baru mengirimnya saat worker menjalankan job itu. Tanpaphp artisan queue:work, job-nya cuma menunggu di tabeljobs, dan HP Pak Darto gak menerima apa pun. Itu jawaban tebakannya: baru sampai saat worker dinyalakan.ShouldBroadcastNowmengirim langsung, tanpa queue.new PrivateChannel('orders.12')menjadi channel bernamaprivate-orders.12(konstruktornya menambahkan awalanprivate-).broadcastWith()menentukan isi pesan. Tanpa itu, semua properti public ikut, termasuk seluruh modelOrder(nomor HP dan lainnya).- Nama event-nya nama class lengkap,
App\Events\OrderStatusChanged, kecuali kamu menulisbroadcastAs(). - Kalau di-dispatch di dalam transaksi database, tambahkan
ShouldDispatchAfterCommit, supaya pesan gak terkirim sebelum datanya benar-benar tersimpan.
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;
});
- Saat frontend berlangganan
private-orders.12, Echo mengirimPOST /broadcasting/authberisisocket_iddanchannel_name. Callback-nya dijalankan dengan user yang sedang login: Pak Darto (pemilik #12) dapat200berisi tanda tangan{"auth":"<key>:<hmac>"}, Mbak Wulan dapat403. User yang belum login langsung ditolak, callback-nya gak pernah jalan. - Channel presence (
new PresenceChannel('papan'), namanyapresence-papan) mengembalikan array data user, bukantrue. Echo memberi daftar yang sedang hadir lewathere, lalujoiningdanleaving. php artisan channel:listmenampilkan semua callback yang terdaftar.
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
REVERB_APP_ID,REVERB_APP_KEY,REVERB_APP_SECRET: kredensial aplikasi.REVERB_HOST/REVERB_PORT: ke mana Laravel mengirim event.REVERB_SERVER_HOST/REVERB_SERVER_PORT: di mana server Reverb sendiri mendengarkan.allowed_originsdiconfig/reverb.phpmembatasi halaman mana yang boleh menyambung (pelajaran Autentikasi WebSocket).- Untuk beberapa server Reverb:
REVERB_SCALING_ENABLED=true, dan mereka saling meneruskan lewat Redis Pub/Sub (pelajaran sebelumnya).
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
})
Echo.private('orders.12')berlanggananprivate-orders.12(dan otomatis memanggil/broadcasting/auth)..listen('OrderStatusChanged')cukup memakai nama pendek, karena Echo menambahkan namespace bawaanApp.EventsmenjadiApp\Events\OrderStatusChanged(dicek di kode laravel-echo 2.6.1). Kalau event memakaibroadcastAs()yang mengembalikan'order.status', dengarkan'.order.status'(pakai titik di depan).- Di React:
useEcho('orders.12', 'OrderStatusChanged', (e) => ...)dari@laravel/echo-react, yang otomatis keluar dari channel saat komponen dilepas.
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
- Lupa menjalankan
queue:work.ShouldBroadcastlewat queue. Gak ada worker, gak ada kabar, dan gak ada error. broadcastAs()tapi.listen()tanpa titik. Echo menambahkanApp\Events\dan mencari nama yang gak pernah dikirim.- Mengirim model utuh. Semua properti public ikut. Pakai
broadcastWith(). - Prefix Redis lupa diperhitungkan. Node subscribe ke
private-orders.12, padahal Laravel mem-publish kelaundry-database-private-orders.12.
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.