WebSocket Rooms and Namespaces — Complete Guide
In this tutorial, you will learn about Websocket Rooms and Namespaces. We cover key concepts, practical examples, and best practices to help you master this topic.
Rooms and namespaces allow you to organize WebSocket connections into logical groups. Rooms enable targeted message delivery to subsets of clients, while namespaces provide full channel isolation.
What You'll Learn
- What rooms are and how to use them
- What namespaces are and when to use them
- Room vs namespace trade-offs
- Implementing rooms with ws and Socket.IO
- Broadcasting within groups
Why It Matters
Without rooms and namespaces, all WebSocket clients receive all messages. This does not scale and creates security and performance problems in multi-channel applications.
Real-World Use
A live auction platform uses rooms for each auction item. Bidders join the room for the item they are interested in and receive only relevant bid updates. The platform has thousands of rooms with millions of concurrent connections.
Flow Chart
flowchart TD
A[Server] --> B[Namespace: /chat]
A --> C[Namespace: /notifications]
B --> D[Room: general]
B --> E[Room: random]
C --> F[Room: user-123]
C --> G[Room: user-456]
D --> H[Client A]
D --> I[Client B]
E --> J[Client C]
F --> K[Client A]
G --> L[Client B]
Code Examples
Example 1: Rooms with ws Library
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
server.on('connection', (ws) => {
ws.rooms = new Set();
ws.on('message', (message) => {
const data = JSON.parse(message);
switch (data.type) {
case 'join':
ws.rooms.add(data.room);
if (!rooms.has(data.room)) {
rooms.set(data.room, new Set());
}
rooms.get(data.room).add(ws);
broadcastToRoom(data.room, {
type: 'notification',
text: 'New user joined',
});
break;
case 'leave':
ws.rooms.delete(data.room);
rooms.get(data.room)?.delete(ws);
break;
case 'message':
if (data.room) {
broadcastToRoom(data.room, {
type: 'message',
text: data.text,
room: data.room,
});
}
break;
}
});
ws.on('close', () => {
ws.rooms.forEach((room) => {
rooms.get(room)?.delete(ws);
});
});
});
function broadcastToRoom(room, data) {
const clients = rooms.get(room);
if (clients) {
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
}
}
Expected output: Clients can join/leave rooms and send messages only to clients in the same room.
Example 2: Socket.IO Namespaces
const { Server } = require('socket.io');
const io = new Server(3000);
// Default namespace
io.on('connection', (socket) => {
socket.emit('welcome', 'Connected to default namespace');
});
// Chat namespace
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
console.log('Client connected to /chat:', socket.id);
socket.on('join', (room) => {
socket.join(room);
chatNamespace.to(room).emit('notification', {
text: `${socket.id} joined ${room}`,
});
});
socket.on('message', ({ room, text }) => {
chatNamespace.to(room).emit('message', {
sender: socket.id,
text,
});
});
});
// Admin namespace with authentication
const adminNamespace = io.of('/admin');
adminNamespace.use((socket, next) => {
const token = socket.handshake.auth.token;
if (token === 'admin-secret') {
next();
} else {
next(new Error('Unauthorized'));
}
});
adminNamespace.on('connection', (socket) => {
socket.join('admin-room');
adminNamespace.to('admin-room').emit('admin-event', {
text: 'Admin connected',
});
});
Expected output: Multiple namespaces with different behavior, including an admin namespace that requires authentication.
Example 3: Dynamic Room Management
const { Server } = require('socket.io');
const io = new Server(3000);
io.on('connection', (socket) => {
// Join rooms based on user data
const userId = socket.handshake.auth.userId;
const role = socket.handshake.auth.role;
// User-specific room
socket.join(`user:${userId}`);
// Role-based room
socket.join(`role:${role}`);
// Subscribe to document rooms
socket.on('subscribe-document', (docId) => {
socket.join(`doc:${docId}`);
socket.documents = socket.documents || [];
socket.documents.push(docId);
io.to(`doc:${docId}`).emit('user-joined', {
userId,
activeUsers: getRoomUsers(`doc:${docId}`),
});
});
socket.on('unsubscribe-document', (docId) => {
socket.leave(`doc:${docId}`);
});
// Room statistics
socket.on('room-stats', (room) => {
const clients = io.sockets.adapter.rooms.get(room);
socket.emit('room-stats', {
room,
users: clients ? clients.size : 0,
});
});
// Cleanup on disconnect
socket.on('disconnect', () => {
if (socket.documents) {
socket.documents.forEach((docId) => {
io.to(`doc:${docId}`).emit('user-left', {
userId,
});
});
}
});
});
function getRoomUsers(room) {
const clients = io.sockets.adapter.rooms.get(room);
if (!clients) return [];
return Array.from(clients).map((id) => {
return io.sockets.sockets.get(id)?.handshake.auth.userId;
}).filter(Boolean);
}
Expected output: Dynamic room management based on user attributes, document subscriptions, and automatic cleanup on disconnect.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not cleaning up rooms on disconnect | Clients that disconnect without leaving rooms leave stale references |
| Using too many namespaces | Namespaces create separate channels; use rooms for fine-grained grouping within a namespace |
| Broadcasting to all when room-specific is needed | Send messages to specific rooms, not global broadcast, to avoid overwhelming clients |
| Ignoring room member limits | Some implementations have limits on room membership; know your platform limits |
| Mixing room concepts between libraries | Room implementation differs between ws and Socket.IO; do not mix APIs |
Practice Questions
- What is the difference between a room and a namespace?
- How do you implement rooms with the ws library?
- How does Socket.IO handle room membership tracking?
- When should you use namespaces vs rooms?
- How do you broadcast to a specific room in Socket.IO?
Challenge
Build a multi-tenant SaaS application with Socket.IO where each tenant has its own namespace. Within each namespace, there are rooms for projects, team communication, and notifications. Implement tenant isolation so messages never leak between tenants.
FAQ
Mini Project
Build a multi-channel chat application with Socket.IO namespaces for different departments (engineering, sales, support) and rooms for specific topics within each department. Include the ability to join, leave, and list active rooms.
What's Next
Learn about WebSocket broadcasting patterns
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro