Anda membuat dashboard pesanan. Setiap kali ada pesanan baru, admin harus menekan tombol refresh untuk melihatnya. Untuk memperbaikinya, banyak developer pemula menambahkan setInterval yang memanggil server tiap tiga detik. Hasilnya jalan, tetapi server dibombardir ratusan request kosong per menit hanya untuk mendengar jawaban "belum ada yang baru". WebSocket dibuat untuk masalah ini: server yang mengabari browser saat ada kejadian, bukan browser yang terus bertanya.
Bagaimana WebSocket Berbeda dari HTTP Biasa
HTTP bekerja dengan pola tanya-jawab. Browser mengirim request, server menjawab, lalu koneksi selesai. Server tidak bisa memulai percakapan. Itulah mengapa teknik lama seperti polling (bertanya berulang) dan long polling (bertanya lalu menunggu lama) muncul sebagai akal-akalan.
WebSocket memulai dengan request HTTP biasa yang meminta "upgrade", lalu koneksi TCP yang sama dibiarkan terbuka dua arah. Selama koneksi hidup, server maupun browser bebas mengirim pesan kapan saja tanpa header berulang. Overhead per pesan hanya beberapa byte, sehingga cocok untuk notifikasi, chat, status pesanan, papan skor, dan indikator "sedang mengetik".
Kapan WebSocket layak dipakai
- Layak: notifikasi langsung, chat, dashboard monitoring, kolaborasi dokumen, antrean pesanan dapur.
- Tidak perlu: data yang cukup diperbarui tiap beberapa menit, laporan bulanan, atau halaman yang jarang berubah. Polling sederhana tiap 30 detik lebih murah dan lebih mudah dirawat.
- Alternatif ringan: Server-Sent Events untuk aliran satu arah (server ke browser) tanpa perlu server WebSocket terpisah.
Arsitektur Broadcasting di Laravel
Laravel tidak menjalankan WebSocket di dalam proses PHP-FPM biasa, karena PHP-FPM dirancang untuk request pendek. Sebagai gantinya ada tiga bagian yang bekerja sama:
- Event di aplikasi Anda, misalnya
PesananDibuat, yang ditandaiShouldBroadcast. - Server WebSocket yang menjaga koneksi terbuka. Sejak Laravel 11 tersedia Reverb, server first-party yang ditulis dengan PHP dan ReactPHP. Pilihan lain: Pusher, Ably, atau Soketi.
- Laravel Echo di sisi browser yang berlangganan channel dan menjalankan callback saat pesan datang.
Alurnya: aplikasi men-dispatch event, event masuk antrean, worker mengirimkannya ke server Reverb, lalu Reverb meneruskan ke semua browser yang berlangganan channel tersebut.
Langkah Praktis: Notifikasi Pesanan Realtime
1. Pasang broadcasting dan Reverb
Di Laravel 11 broadcasting tidak aktif secara bawaan. Satu perintah memasangnya beserta Reverb:
php artisan install:broadcasting
# jawab "yes" saat ditanya soal Reverb, lalu pastikan .env terisi:
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=local-app
REVERB_APP_KEY=kunci-acak-anda
REVERB_APP_SECRET=rahasia-acak-anda
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http
Jalankan server di terminal terpisah selama pengembangan:
php artisan reverb:start --debug
2. Buat event yang di-broadcast
php artisan make:event PesananDibuat
<?php
namespace App\Events;
use App\Models\Pesanan;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PesananDibuat implements ShouldBroadcast
{
use Dispatchable, SerializesModels;
public function __construct(public Pesanan $pesanan) {}
public function broadcastOn(): array
{
// Channel privat: hanya admin yang boleh mendengarkan.
return [new PrivateChannel('admin.pesanan')];
}
public function broadcastWith(): array
{
// Kirim data seperlunya saja, jangan seluruh model.
return [
'id' => $this->pesanan->id,
'nama' => $this->pesanan->nama_pelanggan,
'total' => $this->pesanan->total,
];
}
}
Panggil dari tempat pesanan disimpan:
$pesanan = Pesanan::create($data);
PesananDibuat::dispatch($pesanan);
3. Otorisasi channel privat
Channel privat butuh aturan siapa yang boleh bergabung. Tanpa langkah ini, siapa pun yang tahu nama channel bisa menguping data pesanan. Daftarkan di routes/channels.php:
Broadcast::channel('admin.pesanan', function ($user) {
return $user->role === 'admin';
});
4. Dengarkan di browser dengan Echo
npm install --save-dev laravel-echo pusher-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 ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Echo.private('admin.pesanan')
.listen('PesananDibuat', (e) => {
tambahBarisPesanan(e); // perbarui tabel tanpa refresh
});
Nama event yang didengarkan sama dengan nama kelasnya. Bila Anda ingin nama lain, definisikan broadcastAs() di event.
Masalah yang Sering Menjebak
Event tidak sampai karena antrean tidak jalan
Event ShouldBroadcast dikirim lewat queue. Jika QUEUE_CONNECTION=database dan tidak ada worker berjalan, event menumpuk di tabel jobs dan browser tidak menerima apa pun. Jalankan php artisan queue:work. Untuk event yang harus langsung terkirim tanpa worker, gunakan ShouldBroadcastNow, dengan konsekuensi request pengguna menunggu pengiriman selesai.
Mixed content di produksi
Halaman HTTPS tidak boleh membuka koneksi ws:// biasa. Gunakan wss://, artinya server WebSocket perlu berada di belakang reverse proxy (Nginx atau Caddy) yang menangani TLS. Konfigurasi Nginx harus menyertakan header Upgrade dan Connection, kalau tidak koneksi akan terputus setelah handshake.
Proses harus terus hidup
reverb:start adalah proses panjang. Di server, jalankan di bawah Supervisor atau systemd agar otomatis hidup lagi saat crash atau reboot. Setelah deploy, kirim php artisan reverb:restart agar kode baru terbaca.
Shared hosting umumnya tidak cocok
Shared hosting biasanya melarang proses yang berjalan terus dan tidak membuka port kustom. Jika proyek Anda berada di sana, pilihannya VPS kecil atau layanan terkelola seperti Pusher atau Ably, yang cukup mengubah BROADCAST_CONNECTION dan kredensial tanpa mengubah kode event.
Menguji Tanpa Menunggu Browser
Anda tidak perlu membuka dua tab setiap kali menguji. Gunakan Event::fake() di test untuk memastikan event benar-benar di-dispatch saat pesanan dibuat, lalu uji otorisasi channel secara terpisah.
public function test_pesanan_baru_memicu_broadcast(): void
{
Event::fake([PesananDibuat::class]);
$this->actingAs(User::factory()->create(['role' => 'admin']))
->post('/pesanan', ['nama_pelanggan' => 'Budi', 'total' => 50000]);
Event::assertDispatched(PesananDibuat::class);
}
Untuk melihat pesan yang sebenarnya lewat, jalankan reverb:start --debug. Terminal akan mencetak tiap koneksi masuk, langganan channel, dan pesan yang diteruskan, sehingga mudah memisahkan masalah di backend dari masalah di sisi browser.
Checklist Sebelum Go-Live
- Channel sensitif memakai
PrivateChanneldengan aturan diroutes/channels.php. broadcastWith()hanya mengirim field yang dibutuhkan.- Worker queue dan Reverb berjalan di bawah Supervisor.
- Koneksi memakai
wss://lewat reverse proxy dengan header upgrade. - Browser punya cadangan: bila koneksi putus, halaman tetap bisa memuat ulang data lewat tombol refresh atau polling pelan.
Fitur realtime paling berguna bila data yang dikirim juga efisien. Pelajari caching di Laravel untuk mengurangi beban query saat banyak klien memperbarui tampilan, dan queue dan job Laravel untuk memastikan worker berjalan stabil.