Rooms & Namespaces — Realtime

Rooms & Namespaces Tidak semua user perlu terima semua events. Rooms dan namespaces mengorganisasi koneksi. Rooms Group sockets dalam satu namespace. Client…

Room adalah kelompok koneksi di server, supaya server bisa mengirim event ke sebagian client saja. Namespace adalah jalur terpisah di satu server Socket.IO, dengan aturan masuk, handler, dan room-nya sendiri. Bayangin Laundry Bu Rini punya dua pintu: pintu depan untuk pelanggan dan pintu belakang khusus staf, masing-masing dengan penjaga sendiri (namespace). Di ruang pelanggan ada kotak surat per order: kabar untuk order #12 dimasukkan ke kotak #12, dan cuma yang memegang kunci kotak itu yang membacanya (room). Kuncinya dibagikan petugas, pelanggan gak bisa bikin kunci sendiri.

🧪 Tebakan cepat: Supaya HP Pak Darto menerima kabar order #12, kode di browser-nya memanggil socket.join('order:12'). Apa yang terjadi? Jawabannya ada di bagian "Room cuma ada di server".

Room: kelompok koneksi di server

Server realtime di port 3000 (socket.io 4.8.4):

io.on('connection', (socket) => {
  socket.on('ikuti', (orderId, ack) => {
    // di aplikasi sungguhan: cek dulu HP ini memang pemilik order itu
    socket.join(`order:${orderId}`)
    ack({ ok: true })
  })
  socket.on('berhenti', (orderId) => socket.leave(`order:${orderId}`))
})

// saat Mas Eko menandai #12 Siap diambil:
io.to('order:12').emit('order:status', { id: 12, status: 'Siap diambil', seq: 4 })

HP Pak Darto:

const socket = io('http://localhost:3000')

socket.on('connect', () => {
  socket.emit('ikuti', 12, (res) => console.log(res)) // { ok: true }
})
socket.on('order:status', (e) => tampilkan(e))

Room cuma ada di server

Client Socket.IO gak punya join. Dicoba dengan socket.io-client 4.8.4: socket.join('order:12') di browser melempar TypeError: socket.join is not a function. Itu jawaban tebakannya. Client hanya bisa meminta lewat event (ikuti), dan server yang memutuskan. Ini bagus untuk keamanan: server bisa memeriksa dulu, supaya Mbak Wulan gak bisa ikut room order Pak Darto.

Setiap socket juga otomatis masuk room bernama id-nya sendiri. Begitu tersambung, socket.rooms berisi Set { 'VyInQSLHhBlqqnWFAAAB' }, jadi io.to(socket.id).emit(...) mengirim ke satu koneksi saja.

Mengirim ke siapa

Kode di server Yang menerima
io.emit(...) semua socket di namespace /
io.to('order:12').emit(...) semua anggota room, termasuk pengirimnya kalau ia anggota
socket.to('order:12').emit(...) anggota room, kecuali socket itu sendiri
socket.broadcast.emit(...) semua di namespace-nya, kecuali socket itu sendiri
io.to(socketId).emit(...) satu koneksi
io.to('order:12').to('order:13').emit(...) gabungan dua room; yang ada di keduanya menerima sekali

Semuanya dicoba dengan socket.io 4.8.4.

Namespace: pintu terpisah untuk staf

Bu Rini dan Mas Eko memakai namespace /staff. Pelanggan tetap di /.

const staf = io.of('/staff')

staf.use(async (socket, next) => {
  const ok = await cekTokenStaf(socket.handshake.auth.token) // diperiksa server
  if (!ok) return next(new Error('Bukan staf'))
  next()
})

staf.on('connection', (socket) => {
  socket.on('order:set-status', (d, ack) => {
    // simpan ke database dulu, lalu:
    io.to(`order:${d.id}`).emit('order:status', d) // HP pelanggan, namespace /
    socket.broadcast.emit('order:status', d)        // staf lain di /staff
    ack({ ok: true })
  })
})

Tablet Mas Eko tersambung dengan io('http://localhost:3000/staff', { auth: { token } }).

Kapan room, kapan namespace

Singkatnya, namespace memisahkan logic di level koneksi, dan room mengelompokkan client di dalam namespace yang sama.

Tanpa Socket.IO: room dengan ws biasa

WebSocket biasa gak punya room. Kamu buat sendiri dengan Map (dicoba dengan ws 8.22.0):

import { WebSocketServer, WebSocket } from 'ws'

const wss = new WebSocketServer({ port: 3000 })
const rooms = new Map() // 'order:12' -> Set berisi socket

function join(ws, room) {
  if (!rooms.has(room)) rooms.set(room, new Set())
  rooms.get(room).add(ws)
}
function leave(ws, room) {
  const set = rooms.get(room)
  if (!set) return
  set.delete(ws)
  if (set.size === 0) rooms.delete(room) // room kosong dibuang
}
function toRoom(room, pesan, kecuali) {
  const teks = JSON.stringify(pesan)
  for (const ws of rooms.get(room) ?? []) {
    if (ws !== kecuali && ws.readyState === WebSocket.OPEN) ws.send(teks)
  }
}

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data)
    if (msg.type === 'join') join(ws, `order:${msg.id}`)
  })
  ws.on('close', () => {
    for (const room of [...rooms.keys()]) leave(ws, room)
  })
})

⚠️ Jebakan yang sering ketemu

TL;DR: Room adalah kelompok koneksi yang cuma ada di server: socket.join('order:12') dan leave dipanggil server, io.to('order:12').emit(...) mengirim ke anggotanya, socket.to(...) dan socket.broadcast mengecualikan pengirim, dan tiap socket punya room bernama id-nya sendiri. Room dibuang saat kosong, dan socket keluar dari semua room saat putus, jadi minta join lagi di handler connect. Namespace (io.of('/staff')) adalah jalur terpisah dengan middleware, handler, dan room sendiri, berbagi satu koneksi WebSocket. Namespace memisahkan logic di level koneksi; room mengelompokkan client di dalam satu namespace. Dengan ws biasa, room adalah Map dari nama ke Set socket yang kamu rawat sendiri.

Yang akan kamu pelajari