97. Studi Kasus: Layanan Chat Real-Time
Sebagai seorang engineer, tantangan membangun layanan chat real-time selalu menarik dibahas. Menghadirkan komunikasi dua arah dengan latensi rendah, skalabilitas, serta keamanan memadai, merupakan kombinasi yang kompleks namun sangat fundamental di era digital saat ini. Pada artikel ini, saya akan membedah studi kasus membangun layanan chat real-time dengan websocket menggunakan Node.js dan React, lengkap dengan contoh kode, simulasi, serta ilustrasi alur data. Semoga bermanfaat!
1. Latar Belakang: Mengapa Real-Time Chat?
Dewasa ini, real-time chat service kian merajalela: dari aplikasi messaging, customer support, hingga fitur komentar live pada event streaming. Solusi real-time menjadi kunci produktivitas dan keterlibatan pengguna. Pada kasus ini, kita ingin membangun layanan chat sederhana, group chat dengan fitur sebagai berikut:
- Setiap user dapat join ke satu atau lebih room
- Pesan dikirim dan diterima secara real-time
- Chat history disimpan
- Handle user join/leave notification
2. Arsitektur Singkat
Arsitektur yang akan kita gunakan sebagai berikut:
- Client: React (websocket client)
- Server: Node.js with Express & ws (WebSocket library)
- Database: MongoDB (penyimpanan chat history & user-room relation)
Diagram arsitektur:
flowchart LR
User1 --Socket--> Server
User2 --Socket--> Server
Server --REST(GET/POST)--> MongoDB
Server --Socket Broadcast--> User1
Server --Socket Broadcast--> User2
Server jadi “relay” dua-arah: melayani koneksi socket dari klien, meneruskan pesan ke pengguna lain di room yang sama, dan menyimpan chat ke database.
3. Desain Skema Data
Berikut contoh skema MongoDB koleksi chat:
| Field | Tipe | Keterangan |
|---|---|---|
| _id | ObjectId | Primary key |
| room_id | String | ID ruang |
| sender | String | User yang mengirim pesan |
| message | String | Isi pesan |
| created_at | ISODate | Timestamp waktu dikirim |
Dan relasi user ke room (dengan koleksi user_rooms):
| Field | Tipe | Keterangan |
|---|---|---|
| _id | ObjectId | Primary key |
| user_id | String | ID user |
| room_id | String | ID ruang |
4. Implementasi Backend dengan Node.js
Kita mulai dengan server WebSocket.
// index.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { MongoClient } = require('mongodb');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// Koneksi MongoDB
const mongoClient = new MongoClient('mongodb://localhost:27017');
let chatCollection;
mongoClient.connect().then(client => {
const db = client.db('realtime_chat_demo');
chatCollection = db.collection('chats');
});
// Map user ke ws
const rooms = {};
wss.on('connection', (ws) => {
ws.on('message', async (data) => {
const msg = JSON.parse(data);
if (msg.type === 'JOIN') {
ws.room = msg.room_id;
// Register socket ke room
rooms[ws.room] = rooms[ws.room] || [];
rooms[ws.room].push(ws);
ws.send(JSON.stringify({ type: 'NOTIF', text: `Welcome to room ${ws.room}` }));
}
else if (msg.type === 'CHAT') {
// Simpan ke MongoDB
await chatCollection.insertOne({
room_id: ws.room,
sender: msg.sender,
message: msg.message,
created_at: new Date()
});
// Broadcast ke seluruh ws di room tsb
(rooms[ws.room] || []).forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'CHAT',
sender: msg.sender,
message: msg.message,
room_id: ws.room
}));
}
});
}
});
ws.on('close', () => {
if (ws.room && rooms[ws.room]) {
rooms[ws.room] = rooms[ws.room].filter(client => client !== ws);
}
});
});
server.listen(9000, () => {
console.log('Listening on port 9000');
});
Penjelasan Cepat
- Saat client mengirim tipe
JOIN, server menambahkan socket ke room tertentu. - Jika pesan
CHAT, server menyimpan ke database & meneruskan pesan ke seluruh ws di room tersebut. - Broadcast dilakukan lokal pada memori server (untuk yang skala kecil hingga menengah).
5. Client Side: React + WebSocket
Komponen React untuk connect dan chat secara real-time.
// ChatRoom.jsx
import React, { useState, useEffect, useRef } from 'react';
const WS_URL = 'ws://localhost:9000';
export default function ChatRoom({ user, room_id }) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket(WS_URL);
ws.current.onopen = () => {
ws.current.send(JSON.stringify({ type: 'JOIN', room_id }));
};
ws.current.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'CHAT' || msg.type === 'NOTIF') {
setMessages((prev) => [...prev, msg]);
}
};
return () => ws.current.close();
}, [room_id]);
const sendMessage = () => {
ws.current.send(JSON.stringify({
type: 'CHAT',
sender: user,
message: input
}));
setInput('');
};
return (
<div>
<ul>
{messages.map((msg, i) =>
<li key={i}><b>{msg.sender || 'System'}</b>: {msg.message || msg.text}</li>
)}
</ul>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={sendMessage}>Send</button>
</div>
);
}
Simulasi Penggunaan
- Jalankan server.
- Buka dua tab browser, join ke
room_idyang sama, misal “room1”. - Tulis dan kirim pesan, pesan akan muncul di kedua tab secara real-time.
6. Penanganan Skala: Broadcast dan Distribusi
Sistem awal di atas cukup untuk puluhan hingga ratusan user aktif. Namun, pada skala ribuan koneksi, memori dan proses broadcast socket harus didistribusi.
Prinsipnya:
- Jika server websocket lebih dari satu, relay pesan memakai message broker (misal Redis Pub/Sub).
- Pengiriman pesan antar node server dilakukan secara sinkron, agar user room yang berbeda server tetap real-time.
Diagram alur dengan Redis sebagai pub/sub:
sequenceDiagram
participant UserA as Client A
participant Node1 as WebSocket Node 1
participant Node2 as WebSocket Node 2
participant Redis as Redis Pub/Sub
UserA->>Node1: Send message (room1)
Node1->>Redis: Publish (room1, message)
Redis->>Node2: Subscribe (room1, message)
Node2->>UserB: Forward message
Dengan arsitektur seperti ini, setiap server hanya perlu broadcast ke client yang terkoneksi padanya, dan data lintas node tetap sinkron.
7. Tabel Pro & Kontra Solusi WebSocket Sederhana
| Aspek | Kelebihan | Kekurangan |
|---|---|---|
| Real-time | Latency sangat rendah | Potensi bottleneck di satu node |
| Sederhana | Implementasi mudah, cepat development | Perlu redesign untuk high scale |
| Skalabilitas | Mudah scaling horizontal dengan pub/sub | Tambahan infra message broker |
| Store Chat History | Bebas integrasi DB NoSQL | Perlu pembatasan growth DB |
| Keamanan | Mudah ditambah authentication/authorization via ws handshake | Perlu secure channel (wss, token, dsb) |
8. Kesimpulan
Membangun layanan chat real-time membutuhkan pemahaman komponen networking, database, hingga skalabilitas sistem. Studi kasus di atas memberikan gambaran dasar implementasi chat berbasis websocket, dari backend hingga client. Tentu saja, pada industri atau skala produksi, dibutuhkan concern lebih pada aspek otentikasi, rate-limit, message retention, logging, monitoring, dan lain-lain. Tetapi, fondasi yang terbangun dari studi kasus ini cukup kokoh untuk dikembangkan menjadi produk chat yang scalable!
Bagaimana dengan arsitektur real-time chat yang pernah kamu bangun? Jangan ragu diskusikan di kolom komentar!
Happy coding, and keep sharing knowledge!