Membangun Real-Time Chat App dengan WebSocket dan Socket.io di Node.js
Mengenal Keterbatasan HTTP Request-Response untuk Chat Real-Time
Sebagian besar aplikasi web modern masih bergantung pada protokol HTTP dengan pola request-response. Dalam skenario chat, pendekatan ini menimbulkan masalah serius. Setiap kali pengguna ingin mendapat pesan baru, client harus mengirim permintaan HTTP ke server secara berulang. Teknik ini dikenal sebagai polling, dan teknik ini bekerja dengan cara yang sangat boros.
Polling memaksa server untuk menerima dan merespons ratusan bahkan ribuan permintaan kosong hanya untuk mengecek apakah ada pesan baru. Bayangkan sepuluh pengguna dalam satu ruang chat. Masing-masing mengirim permintaan setiap dua detik. Server harus memproses tiga ratus permintaan per menit untuk layanan yang hanya mengirim beberapa pesan. Latensi pun tidak bisa dihindari. Ada jeda antara waktu pesan dikirim dan waktu client menerimanya, karena data hanya bergerak saat client bertanya.

Gambar: Ilustrasi pola komunikasi HTTP secara tradisional (forever frame) yang membutuhkan permintaan berulang dari client ke server — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Forever_frame_diagram.png)
WebSocket hadir untuk memecahkan masalah ini. Protokol ini menyediakan koneksi persistent dan bidirectional antara client dan server. Setelah koneksi terbentuk, kedua pihak bisa saling mengirim data kapan saja tanpa perlu membuka koneksi baru. Tidak ada overhead HTTP header, tidak ada polling yang membuang sumber daya. Hasilnya, chat berjalan secara instan dengan latensi yang sangat rendah. Di sinilah Socket.io berperan sebagai library yang membungkus WebSocket dengan fitur tambahan seperti fallback transport, room management, dan event system yang intuitif.

Gambar: Diagram komunikasi WebSocket yang menunjukkan koneksi bidirectional dan persistent antara client dan server — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Websocket.png)
Menyiapkan Server Node.js dengan Socket.io
Kita mulai dengan menyiapkan proyek baru dan menginstal dependensi yang diperlukan.
npm init -y
npm install express socket.ioExpress digunakan sebagai HTTP server dasar, sementara Socket.io menangani komunikasi real-time di atasnya. Keduanya bekerja berdampingan. Express melayani rute HTTP biasa, sedangkan Socket.io mendengarkan event WebSocket pada server yang sama.
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*',
},
});
app.get('/', (req, res) => {
res.send('Chat server running');
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});Output:
Server running on port 3000Perhatikan alur inisialisasi di atas. Kita membuat server HTTP melalui http.createServer(app), lalu meneruskan instance tersebut ke konstruktor Socket.io. Dengan cara ini, server HTTP dan WebSocket berbagi port yang sama. Ini penting agar kita tidak perlu membuka port tambahan untuk koneksi real-time.
Setelah kode di atas berjalan, kita akan melihat pesan "Server running on port 3000" di konsol. Ini menandakan bahwa Express sudah aktif dan Socket.io siap menerima koneksi WebSocket dari client.
Perhatikan konfigurasi CORS pada objek Server. Nilai origin: '*' memang praktis untuk pengembangan lokal. Namun di production, kita harus membatasi origin ke domain tertentu. Ini mencegah situs lain menggunakan koneksi WebSocket server kita secara tidak sah. CORS pada WebSocket tetap relevan karena permintaan koneksi diawali dengan handshake HTTP sebelum di-upgrade ke protokol WebSocket.
MERN Stack Development
Launch your journey into full-stack web development with this comprehensive, pro...
Mengelola Event Koneksi dan Diseminasi Pesan di Server
Inti dari aplikasi chat real-time terletak pada event system Socket.io. Server mendengarkan dua event utama: connection saat client tersambung, dan disconnect saat client terputus. Di antara keduanya, kita bisa mendefinisikan event kustom untuk menerima dan menyebarkan pesan.
const messages = [];
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('chat message', (msg) => {
const messageData = {
id: socket.id,
text: msg,
timestamp: new Date().toISOString(),
};
messages.push(messageData);
io.emit('chat message', messageData);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});Event chat message adalah event kustom yang kita definisikan sendiri. Saat client mengirim event ini bersama data pesan, server menerima data tersebut, menyimpannya di array messages, lalu menyebarkannya ke semua client yang terhubung melalui io.emit(). Mekanisme ini menjadikan server sebagai relay pusat: satu client mengirim, semua client menerima.
Coba perhatikan alur kerjanya. Client A mengirim event chat message dengan teks "Halo semua". Server menerima data itu, menambahkan timestamp, lalu memanggil io.emit('chat message', data). Semua client, termasuk pengirim, akan menerima event yang sama. Untuk membedakan pengirim dari penerima, kita bisa menggunakan socket.id atau data user yang dikirim bersama pesan.
Socket.io juga mendukung pola acknowledgment. Client bisa mengirimkan callback sebagai argumen terakhir saat memancarkan event, dan server bisa memanggil callback itu setelah selesai memproses. Pola ini berguna untuk konfirmasi pengiriman, validasi sisi server, atau mengembalikan hasil pemrosesan. Dengan acknowledgment, kita bisa menampilkan status seperti centang "terkirim" di antarmuka chat tanpa perlu event roundtrip tambahan.
Membangun Client Chat dengan HTML dan Socket.io Client
Di sisi client, kita perlu membuat antarmuka chat dan menghubungkannya ke server menggunakan Socket.io client library. Library ini bisa diakses langsung melalui CDN yang disediakan oleh Socket.io.
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
#messages { list-style: none; padding: 0; margin-bottom: 10px; }
#messages li { padding: 8px 12px; margin-bottom: 6px; background: #f0f0f0; border-radius: 6px; }
#form { display: flex; gap: 8px; }
#input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 6px; }
button { padding: 10px 20px; background: #4a90d9; color: white; border: none; border-radius: 6px; cursor: pointer; }
</style>
</head>
<body>
<h3>Chat Room</h3>
<ul id="messages"></ul>
<form id="form">
<input id="input" autocomplete="off" placeholder="Ketik pesan..." />
<button>Kirim</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const form = document.getElementById('form');
const input = document.getElementById('input');
const messages = document.getElementById('messages');
form.addEventListener('submit', (e) => {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
socket.on('chat message', (data) => {
const item = document.createElement('li');
item.textContent = data.text;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>Client-side code di atas menghubungkan browser ke server Socket.io melalui io() tanpa parameter URL, yang secara otomatis mengarah ke host yang sama dengan halaman HTML. Setelah koneksi terbentuk, pengguna bisa mengetik pesan dan mengirimkannya melalui socket.emit('chat message', value). Pesan yang masuk dari server akan dirender sebagai elemen li baru di dalam daftar pesan.
Hal yang menarik dari implementasi ini adalah sinkronisasi terjadi secara otomatis. Jika kita membuka dua tab browser dan mengirim pesan dari salah satunya, pesan akan muncul di kedua tab secara instan tanpa perlu refresh halaman. Inilah kekuatan WebSocket yang dipadukan dengan event system Socket.io.
Mengelola Room dan Private Message dengan Socket.io
Dalam aplikasi chat yang lebih kompleks, kita tidak ingin semua pesan dikirim ke semua pengguna. Setiap percakapan sebaiknya terisolasi dalam ruangnya masing-masing. Socket.io menyediakan fitur room untuk kebutuhan ini.
io.on('connection', (socket) => {
socket.on('join room', (room) => {
socket.join(room);
socket.emit('chat message', {
text: `Anda bergabung dengan ruang: ${room}`,
system: true,
});
socket.to(room).emit('chat message', {
text: `Pengguna ${socket.id} bergabung ke ruang ini`,
system: true,
});
});
socket.on('room message', ({ room, msg }) => {
const messageData = {
id: socket.id,
text: msg,
timestamp: new Date().toISOString(),
};
io.to(room).emit('chat message', messageData);
});
});Method socket.join(room) menambahkan socket client ke dalam sebuah ruang. Setelah itu, kita bisa mengirim pesan ke semua anggota ruang menggunakan io.to(room).emit(). Perhatikan bahwa socket.to(room) mengirim ke semua client dalam ruang kecuali pengirim, sedangkan io.to(room) mengirim ke semua client termasuk pengirim.
Konsep ini berguna untuk berbagai skenario. Dalam chat group, kita bisa membuat satu room per grup. Dalam private message, kita bisa membuat room dengan nama yang menggabungkan ID kedua pengguna. Semua pesan akan tetap terisolasi sesuai dengan room masing-masing.
Room di Socket.io bersifat ephemeral. Saat client terputus, ia otomatis keluar dari semua room yang diikutinya. Kita tidak perlu membersihkannya secara manual. Socket.io juga mendukung namespace, yaitu level di atas room yang memungkinkan pemisahan logika chat pada jalur terpisah. Contohnya, namespace /support untuk customer service dan /general untuk obrolan publik. Setiap namespace memiliki room management sendiri yang independen satu sama lain.
Menerapkan Validasi dan Keamanan pada Pesan Chat
Fitur chat yang berjalan lancar belum lengkap tanpa lapisan keamanan. Ada beberapa aspek yang harus kita tangani server-side sebelum menyebarkan pesan.
Pertama, validasi konten pesan. Jangan pernah mengandalkan validasi client-side saja. Di server, kita harus memeriksa apakah pesan tidak kosong, tidak melebihi batas panjang karakter, dan hanya mengandung data yang diizinkan.
socket.on('chat message', (msg) => {
if (!msg || typeof msg !== 'string') return;
const sanitized = msg.trim().substring(0, 500);
if (sanitized.length === 0) return;
const messageData = {
id: socket.id,
text: sanitized,
timestamp: new Date().toISOString(),
};
io.emit('chat message', messageData);
});Kedua, sanitasi input untuk mencegah XSS (Cross-Site Scripting). Jika pesan ditampilkan sebagai innerHTML di browser, pengguna jahat bisa menyisipkan tag script. Solusinya adalah selalu gunakan textContent daripada innerHTML saat merender pesan di client, seperti yang sudah kita lakukan pada contoh client di atas.
Ketiga, terapkan rate limiting pada event emissions. Kita bisa membatasi jumlah pesan yang bisa dikirim oleh satu client dalam periode tertentu. Ini mencegah spam dan potensi serangan DDoS pada server.
const userMessageCount = new Map();
socket.on('chat message', (msg) => {
const now = Date.now();
const userData = userMessageCount.get(socket.id) || { count: 0, resetAt: now + 10000 };
if (now > userData.resetAt) {
userData.count = 0;
userData.resetAt = now + 10000;
}
userData.count++;
if (userData.count > 5) {
socket.emit('error', 'Anda mengirim pesan terlalu cepat. Silakan tunggu.');
return;
}
userMessageCount.set(socket.id, userData);
// ...
});Dengan validasi, sanitasi, dan rate limiting ini, aplikasi chat menjadi lebih aman dan stabil. Pengguna tetap bisa menikmati komunikasi real-time tanpa gangguan dari pihak yang tidak bertanggung jawab.
Selain validasi pesan, kita juga perlu mengamankan koneksi WebSocket itu sendiri. Socket.io mendukung middleware autentikasi melalui method io.use(). Middleware ini berjalan sebelum koneksi diterima, sehingga kita bisa memeriksa token JWT atau session ID yang dikirim client saat handshake. Jika token tidak valid, koneksi ditolak sebelum event apa pun diproses. Ini memastikan hanya pengguna yang sudah terautentikasi yang bisa berkomunikasi melalui server chat.
Tertarik mendalami pengembangan aplikasi real-time lebih lanjut? Di Rumah Coding, kita membahas topik ini secara mendalam dalam konteks pengembangan web full-stack, dari konsep WebSocket hingga deployment aplikasi Node.js ke production. Gabung ke program Web Development atau Node.js Bootcamp untuk pengalaman belajar yang lebih terstruktur.
Course Terkait
MERN Stack Development
Launch your journey into full-stack web development with this comprehensive, project-driven course. Designed for beginners, this course demystifies the MERN stack (MongoDB, Express.js, React.js, Node.js) by guiding you step-by-step in building a real-world application from scratch. By the end of this course, you will have the practical skills and a complete portfolio project to confidently step into the modern web development industry.
EduStream - Mini Learning Management System (LMS)
- Secure Authentication & Authorization: Robust user registration and login using JWT, with strict role-based access control (Admin/Instructor vs. Student).
- Course Management (Admin Dashboard): Full CRUD (Create, Read, Update, Delete) capabilities for administrators to manage course details, including titles, descriptions, pricing, and thumbnail image uploads.
- Public Course Catalog: An interactive and responsive storefront where users can browse available courses.