Belajar WebSocket - Real-Time Notifications System
Episode 21 of 34

Belajar WebSocket - Real-Time Notifications System

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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.

Tipe dan Delivery Channel

Multi-Channel Delivery

Notifikasi tidak selalu sampai lewat WebSocket. Beberapa channel umum:

Channel delivery notifikasi
In-app  : badge dan toast lewat WebSocket
Push    : notifikasi sistem ke device saat offline
Email   : ringkasan atau notifikasi penting
SMS     : kode OTP dan notifikasi kritikal

Aturan praktisnya: WebSocket untuk notifikasi yang harus muncul seketika, push untuk saat aplikasi tertutup, dan email atau SMS sebagai fallback untuk yang penting.

Prioritas Notifikasi

Tidak semua notifikasi setara. Beri label prioritas agar delivery bisa disesuaikan.

JSPrioritas notifikasi
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.

Arsitektur

Notification Service Terpisah

Pisahkan logika notifikasi dari server chat agar tidak mengganggu inti aplikasi.

Arsitektur notifikasi
app server -> message broker -> notification service -> WebSocket server

Aplikasi 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.

Queue dan Retry

Pengiriman yang gagal tidak boleh hilang diam-diam.

JSQueue dengan retry
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.

Preferensi Pengguna

Pengaturan dan Mute

Setiap pengguna punya preferensi sendiri: notifikasi apa, lewat channel apa, dan kapan tidak boleh mengganggu.

JSPreferensi notifikasi pengguna
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.

Do Not Disturb

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.

Optimasi Delivery

Batching Notifikasi

Mengirim satu per satu untuk lima belas like adalah pemborosan.

JSBatch notifikasi per pengguna
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.

Presence-Based Delivery

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.

Persistence

Status Baca dan Belum Dibaca

Notifikasi perlu disimpan agar pengguna bisa melihat riwayat.

JSSimpan status notifikasi
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.

Ekspira dan Arsip

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.

Penutup

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:

  • Notifikasi punya beberapa channel dengan kecepatan dan jangkauan berbeda.
  • Pisahkan notification service dari aplikasi utama.
  • Preferensi dan DND dievaluasi di server agar berlaku lintas device.
  • Batching mengurangi beban jaringan untuk notifikasi berjumlah besar.
  • Presence menentukan apakah perlu push atau cukup in-app.
  • Simpan status baca dan terapkan kebijakan ekspira.

Di episode 22 berikutnya kita membangun live dashboard & data streaming: update frekuensi tinggi, pola delta update, integrasi Chart.js dan D3, serta optimasi rendering.