Building a Multi-Room WebSocket Chat Server with User Presence in Node.js
WebSockets are the go-to technology for real-time web applications. In this tutorial, we’ll take things to the next level by creating a multi-room chat server that supports user presence using Node.js
and the native ws
library.
Overview
This project will feature:
- Multiple chat rooms
- Real-time user join/leave notifications
- Broadcasting messages to specific rooms only
- Tracking and syncing online users per room
Step 1: Set Up Your Project
mkdir multiroom-chat
cd multiroom-chat
npm init -y
npm install ws uuid
Step 2: The WebSocket Server
// server.js
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const wss = new WebSocket.Server({ port: 3000 });
const rooms = {};
function broadcastToRoom(roomId, data, exceptSocket = null) {
rooms[roomId].forEach((client) => {
if (client.readyState === WebSocket.OPEN && client !== exceptSocket) {
client.send(JSON.stringify(data));
}
});
}
wss.on('connection', (ws) => {
let userId = uuidv4();
let roomId = null;
let username = null;
ws.on('message', (message) => {
try {
const msg = JSON.parse(message);
if (msg.type === 'join') {
roomId = msg.room;
username = msg.username;
if (!rooms[roomId]) rooms[roomId] = new Set();
rooms[roomId].add(ws);
broadcastToRoom(roomId, {
type: 'notification',
message: `${username} joined the room.`,
}, ws);
const users = Array.from(rooms[roomId])
.map((client) => client.username)
.filter(Boolean);
ws.username = username;
ws.send(JSON.stringify({ type: 'users', users }));
}
if (msg.type === 'chat') {
broadcastToRoom(roomId, {
type: 'chat',
message: msg.message,
username,
});
}
} catch (err) {
console.error('Invalid message received:', message);
}
});
ws.on('close', () => {
if (roomId && rooms[roomId]) {
rooms[roomId].delete(ws);
broadcastToRoom(roomId, {
type: 'notification',
message: `${username} left the room.`,
});
}
});
});
console.log('WebSocket chat server running on ws://localhost:3000');
Step 3: Sample Client HTML
<!DOCTYPE html>
<html>
<body>
<input id="username" placeholder="Your name">
<input id="room" placeholder="Room name">
<button onclick="joinRoom()">Join Room</button>
<div id="chat"></div>
<input id="msg">
<button onclick="sendMessage()">Send</button>
<script>
let socket;
function joinRoom() {
const name = document.getElementById('username').value;
const room = document.getElementById('room').value;
socket = new WebSocket('ws://localhost:3000');
socket.addEventListener('open', () => {
socket.send(JSON.stringify({ type: 'join', username: name, room }));
});
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
const chat = document.getElementById('chat');
if (data.type === 'chat') {
chat.innerHTML += `<p><strong>${data.username}:</strong> ${data.message}</p>`;
} else if (data.type === 'notification') {
chat.innerHTML += `<p style="color:gray;">${data.message}</p>`;
} else if (data.type === 'users') {
chat.innerHTML += `<p style="color:green;">Users in room: ${data.users.join(', ')}</p>`;
}
});
}
function sendMessage() {
const message = document.getElementById('msg').value;
socket.send(JSON.stringify({ type: 'chat', message }));
}
</script>
</body>
</html>
Conclusion
By building this multi-room chat with user presence from scratch, you've touched on several key aspects of WebSocket server architecture: stateful connections, group-based broadcasting, and dynamic user tracking. This setup can be expanded into more complex systems like collaborative workspaces, gaming lobbies, and live Q&A rooms.
If this guide helped you, consider supporting me: buymeacoffee.com/hexshift
Top comments (0)