Episode ini membangun sistem notifikasi real-time: tipe notifikasi dan delivery channel, arsitektur notification service, preferensi pengguna, batching, serta persistence status baca dan belum dibaca.

Notifikasi real-time adalah alasan utama banyak aplikasi memasang WebSocket: badge berubah, toast muncul, daftar notifikasi bertambah tanpa muat ulang halaman. Tapi di balik tampilan sederhana itu ada sistem dengan banyak keputusan desain.
Episode 21 membangun sistem notifikasi real-time: tipe dan delivery channel, arsitektur notification service, preferensi pengguna, optimasi pengiriman dengan batching, dan penyimpanan status baca. Kalian akan melihat pola yang bisa dipakai ulang di aplikasi apa pun.
Notifikasi tidak selalu sampai lewat WebSocket. Beberapa channel umum:
In-app : badge dan toast lewat WebSocket
Push : notifikasi sistem ke device saat offline
Email : ringkasan atau notifikasi penting
SMS : kode OTP dan notifikasi kritikalAturan praktisnya: WebSocket untuk notifikasi yang harus muncul seketika, push untuk saat aplikasi tertutup, dan email atau SMS sebagai fallback untuk yang penting.
Tidak semua notifikasi setara. Beri label prioritas agar delivery bisa disesuaikan.
const prioritas = {
kritikal: 0,
tinggi: 1,
normal: 2,
rendah: 3,
};kritikal dikirim lewat semua channel tanpa menunggu, rendah bisa di-batch dan dikirim per jam. Prioritas menjadi dasar keputusan delivery di seluruh sistem.
Pisahkan logika notifikasi dari server chat agar tidak mengganggu inti aplikasi.
app server -> message broker -> notification service -> WebSocket serverAplikasi utama mengirim event ke broker, notification service memproses preferensi dan memilih channel, lalu meneruskan ke WebSocket server untuk delivery real-time. Pemisahan ini membuat notifikasi bisa diskalakan sendiri dan diuji terpisah.
Pengiriman yang gagal tidak boleh hilang diam-diam.
const antrean = [];
async function kirim(notif) {
try {
await wsDelivery(notif);
return true;
} catch {
antrean.push(notif);
return false;
}
}antrean.push(notif) menahan notifikasi yang gagal untuk percobaan berikutnya. Gabungkan dengan retry berbatas dan jangan lupa memberi tahu pemantauan jika antrean menumpuk.
Setiap pengguna punya preferensi sendiri: notifikasi apa, lewat channel apa, dan kapan tidak boleh mengganggu.
const pref = await db.preferensi.findUnique({
where: { userId: 42 },
});
const boleh = (notif) => {
if (pref.dnd && pref.dndAktif) return false;
if (pref.muteKategori.includes(notif.kategori)) return false;
return pref.channel[notif.kategori].includes(notif.channel);
};boleh(notif) mengecek tiga hal: mode Do Not Disturb, kategori yang dimute, dan channel yang diizinkan per kategori. Keputusan ini dijalankan di server, bukan klien, agar muting tetap berlaku di semua device.
Mode DND biasanya berbasis waktu, misalnya tidak ada notifikasi non-kritikal antara pukul 22:00 dan 07:00. Simpan jadwal di database dan biarkan sistem menundanya, bukan membuangnya.
Mengirim satu per satu untuk lima belas like adalah pemborosan.
const pending = new Map();
function tambah(notif) {
const list = pending.get(notif.userId) || [];
list.push(notif);
pending.set(notif.userId, list);
}
setInterval(() => {
pending.forEach((list, userId) => {
if (list.length === 1) {
kirimKeUser(userId, list[0]);
} else {
kirimKeUser(userId, {
type: "notif:batch",
items: list,
});
}
});
pending.clear();
}, 5000);pending mengumpulkan notifikasi 5 detik lalu mengirim batch tunggal jika lebih dari satu. Ini mengurangi ratusan frame menjadi puluhan frame tanpa mengorbankan rasa real-time.
Jika pengguna sedang aktif di aplikasi, tidak perlu mengirim push notification — cukup in-app. Sistem memanfaatkan presence dari episode 11: pengguna online menerima lewat WebSocket, yang offline menerima lewat push.
Notifikasi perlu disimpan agar pengguna bisa melihat riwayat.
const tersimpan = await db.notifikasi.create({
data: {
userId: 42,
teks: "Arman membalas komentarmu",
kategori: "komentar",
dibaca: false,
},
});Field dibaca: false memungkinkan query notifikasi yang belum dibaca. Saat pengguna membuka daftar, server menandai semuanya sebagai dibaca dan mengirim event ke klien untuk mereset badge.
Notifikasi menumpuk seiring waktu. Terapkan kebijakan: hapus atau arsipkan notifikasi yang lebih tua dari 90 hari, dan beri batas maksimal notifikasi per pengguna. Ini menjaga database tetap ramping dan query tetap cepat.
Episode 21 merangkai notifikasi dari hulu ke hilir: jenis dan channel, arsitektur terpisah, preferensi yang dihormati, batching yang hemat, dan status yang bertahan di database.
Inti yang harus dibawa pulang:
Di episode 22 berikutnya kita membangun live dashboard & data streaming: update frekuensi tinggi, pola delta update, integrasi Chart.js dan D3, serta optimasi rendering.