Realtime database adalah layanan database yang mengirim perubahan data ke client yang berlangganan, jadi kamu gak menulis server WebSocket sendiri. Supabase melakukannya di atas Postgres, Firebase dengan database dokumennya sendiri. Bayangin buku order Laundry Bu Rini punya petugas yang membaca setiap baris baru di buku harian toko, lalu menelepon setiap orang yang boleh tahu: Bu Rini untuk semua order, Pak Darto cuma untuk order miliknya.
🧪 Tebakan cepat: Mas Eko mengubah status #12 lewat aplikasi Laravel, dengan
UPDATEbiasa ke Postgres, bukan lewat library Supabase. HP Pak Darto berlangganan perubahan tabelorderslewatsupabase-js. Dapatkah HP-nya kabar? Jawabannya ada di bagian "Dari WAL ke HP Pak Darto".
Status order sebagai perubahan baris
Di Supabase, papan laundry cukup satu tabel Postgres. Perubahan status adalah UPDATE satu baris:
create table orders (
id bigint primary key,
nama text, kg int, status text,
pelanggan uuid references auth.users
);
update orders set status = 'Siap diambil' where id = 12;
Tabelnya harus dimasukkan ke publication supabase_realtime (daftar tabel yang perubahannya disiarkan), lalu client berlangganan lewat channel:
import { createClient } from '@supabase/supabase-js' // 2.117.3
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) // kunci publik: boleh di browser, yang menjaga data adalah RLS
supabase
.channel('order-12')
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'orders', filter: 'id=eq.12' },
(payload) => tampilkan(payload.new))
.subscribe((status) => console.log(status)) // 'SUBSCRIBED', lalu kabar mulai datang
alter publication supabase_realtime add table orders;
Setiap UPDATE pada baris #12 sampai ke callback sebagai objek:
{ schema: 'public', table: 'orders', commit_timestamp: '...', eventType: 'UPDATE',
new: { id: 12, nama: 'Pak Darto', kg: 3, status: 'Siap diambil', pelanggan: '...' },
old: { id: 12 }, errors: ... }
filter: 'id=eq.12': cuma baris #12. Operator lain:neq,lt,gt,in(paling banyak 100 nilai),like,is, dan beberapa lagi.oldbawaannya cuma berisi primary key. Nilai lama lengkap butuhalter table orders replica identity full, dan tetap cuma primary key kalau tabelnya memakai RLS. Jangan membandingkanpayload.old.status.
Dari WAL ke HP Pak Darto
Postgres menulis setiap perubahan ke WAL (Write-Ahead Log), catatan harian yang dipakai untuk pemulihan dan replikasi. Supabase Realtime membuka logical replication slot, membaca perubahan dari WAL, memeriksa siapa boleh menerimanya, lalu mengirimnya lewat WebSocket. Gak ada polling ke tabel, dan gak ada trigger yang perlu kamu pasang.
Karena sumbernya WAL, perubahan dari mana pun ikut: Laravel, psql, dashboard, cron. Itu jawaban tebakannya: HP Pak Darto tetap dapat kabar, walaupun Mas Eko gak memakai Supabase.
RLS menentukan siapa dapat apa
RLS (Row Level Security) adalah aturan Postgres per baris: siapa boleh membaca baris yang mana. Untuk Postgres Changes, aturan select inilah yang memutuskan HP siapa menerima kabar.
alter table orders enable row level security;
create policy "pelanggan melihat ordernya" on orders
for select to authenticated
using (pelanggan = (select auth.uid()));
- Satu perubahan diperiksa sekali per pelanggan yang berlangganan: 100 HP, 100 pemeriksaan. Prosesnya satu thread supaya urutan terjaga, jadi menambah ukuran database gak mempercepatnya. Di atas sekitar 3.000 pelanggan pada perubahan yang sama, dokumentasi Supabase menyarankan Broadcast dari database (trigger
realtime.broadcast_changes), yang mengirim sekali lalu menyebarkannya. - RLS menyala tapi gak ada policy: HP pelanggan gak menerima apa pun, tanpa error. Menurut panduan troubleshooting Supabase, ini penyebab paling sering "kabarnya gak datang".
- RLS mati: siapa pun yang punya kunci publik dan berlangganan tanpa filter menerima order semua pelanggan.
Broadcast dan Presence
Channel yang sama punya dua fitur lain:
- Broadcast: kirim pesan antar client tanpa menyentuh database, misalnya "Mas Eko sedang mengetik":
channel.send({ type: 'broadcast', event: 'ketik', payload: { siapa: 'eko' } }). - Presence: siapa yang sedang online, disimpan di memori server Realtime sebagai CRDT. Client memanggil
channel.track({ user: 'eko' }), dan yang lain mendengar.on('presence', { event: 'sync' }, ...).
Firebase: model yang lain
Firestore menyimpan dokumen, bukan baris, dan langganannya onSnapshot:
import { doc, onSnapshot } from 'firebase/firestore' // firebase 13.0.0
const berhenti = onSnapshot(doc(db, 'orders', '12'), (snap) => tampilkan(snap.data()))
- Tulisan dari HP itu sendiri langsung memanggil listener-nya sebelum sampai ke server (latency compensation);
snap.metadata.hasPendingWritesbernilaitrueselama itu. - Aturan keamanannya (Security Rules) bukan filter: query yang mungkin mengembalikan dokumen terlarang ditolak seluruhnya, bukan disaring diam-diam seperti RLS.
- Biayanya per dokumen: satu read setiap dokumen di hasil listener bertambah atau berubah, dan listener yang terputus lebih dari 30 menit dihitung seperti query baru.
Yang kamu tukar
Dicek di dokumentasi Supabase, 11 Oktober 2026:
| Free | Pro | |
|---|---|---|
| Koneksi bersamaan | 200 | 500 (lebihnya $10 per 1.000) |
| Pesan per bulan | 2 juta | 5 juta (lebihnya $2,50 per juta) |
Satu perubahan database dihitung satu pesan per client yang mendengarnya. Laundry Bu Rini: 30 perubahan sehari ke 5 layar = 150 pesan sehari, gratis. 10.000 HP yang berlangganan seluruh tabel tanpa filter: satu perubahan = 10.000 pesan, 300 perubahan sehari = 90 juta pesan sebulan. Dengan filter: 'id=eq.12', tiap perubahan cuma sampai ke HP pemilik order dan layar staf.
Imbalannya: gak ada server realtime yang kamu urus. Harganya: API-nya milik vendor, keamanan bergantung pada policy RLS yang benar, dan biaya naik mengikuti jumlah pendengar.
⚠️ Jebakan yang sering ketemu
- Tabel belum masuk publication. Gak ada kabar, gak ada error.
alter publication supabase_realtime add table orders. - RLS tanpa policy
select, atau sebaliknya RLS mati sehingga order bocor ke semua pelanggan. - Mengandalkan
payload.old. Isinya cuma primary key kalau tabelnya memakai RLS. - Gak mengambil snapshot setelah
SUBSCRIBED. Perubahan sebelum langganan aktif terlewat. Polanya sama dengan pelajaran Dashboard: berlangganan, lalu ambil data awal.
TL;DR: Realtime database mengirim perubahan data ke client tanpa server WebSocket buatanmu. Supabase Realtime membaca WAL Postgres lewat logical replication, jadi UPDATE dari mana pun (termasuk Laravel) sampai ke .on('postgres_changes', { event, schema, table, filter }, cb) sebagai { eventType, new, old, ... }. Tabelnya harus ada di publication supabase_realtime. RLS memutuskan siapa menerima apa, sekali per pelanggan per perubahan; tanpa policy semuanya diam, tanpa RLS semuanya bocor; old cuma primary key. Broadcast untuk pesan antar client dan untuk skala besar, Presence untuk siapa yang online. Firebase memakai dokumen dan onSnapshot, dengan rules yang menolak, bukan menyaring. Biayanya dihitung per pesan per pendengar: filter langganannya.