Rooms and Namespaces
Rooms and namespaces are powerful features in Socket.io that allow you to organize connections and broadcast messages to specific groups of clients. Understanding when to use rooms versus namespaces is crucial for building scalable real-time applications.
What Are Rooms?
Rooms are arbitrary channels that sockets can join and leave. They allow you to broadcast events to a subset of connected clients:
// Think of rooms like chat channels
// A socket can be in multiple rooms simultaneously
// Examples:
// - room:general (chat channel)
// - room:support (chat channel)
// - game:123 (specific game instance)
// - user:john (private room for a user)
Note: Rooms are a server-only concept. Clients cannot directly join rooms - they must request the server to join them to a room. This gives the server full control over room membership.
Joining and Leaving Rooms
Sockets can join and leave rooms using simple methods:
// SERVER
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Join a room
socket.join('general');
console.log(`${socket.id} joined room: general`);
// Join multiple rooms
socket.join(['general', 'announcements', 'vip']);
// Leave a room
socket.leave('general');
// Check if socket is in a room
const isInRoom = socket.rooms.has('general');
console.log('In general room?', isInRoom);
// Get all rooms the socket is in
console.log('Socket rooms:', socket.rooms);
// Set { 'socketId', 'general', 'announcements', 'vip' }
});
Client-Requested Room Joining
Typically, clients request to join specific rooms:
// CLIENT
socket.emit('joinRoom', 'general');
socket.emit('leaveRoom', 'support');
// SERVER
io.on('connection', (socket) => {
// Handle join room request
socket.on('joinRoom', (roomName) => {
// Validate room name
if (!roomName || typeof roomName !== 'string') {
socket.emit('error', 'Invalid room name');
return;
}
// Join the room
socket.join(roomName);
// Notify user
socket.emit('roomJoined', roomName);
// Notify others in the room
socket.to(roomName).emit('userJoined', {
userId: socket.id,
room: roomName
});
console.log(`${socket.id} joined ${roomName}`);
});
// Handle leave room request
socket.on('leaveRoom', (roomName) => {
socket.leave(roomName);
socket.emit('roomLeft', roomName);
socket.to(roomName).emit('userLeft', {
userId: socket.id,
room: roomName
});
});
});
Broadcasting to Rooms
Send messages to all sockets in a specific room:
// Broadcast to everyone in 'general' room
io.to('general').emit('message', 'Hello general room!');
// Broadcast to multiple rooms
io.to('general').to('vip').emit('announcement', 'Special message');
// Broadcast to room except sender
socket.to('general').emit('message', 'Someone else sent this');
// Broadcast to room including sender
io.in('general').emit('message', 'Everyone in general receives this');
// Note: to() and in() are identical - use whichever you prefer
Room Management
Get information about rooms and their members:
// Get all sockets in a room (Socket.io 4.0+)
const socketsInRoom = await io.in('general').fetchSockets();
console.log(`${socketsInRoom.length} users in general`);
socketsInRoom.forEach(socket => {
console.log('Socket ID:', socket.id);
console.log('Rooms:', socket.rooms);
});
// Get all room names
const rooms = io.sockets.adapter.rooms;
console.log('All rooms:', Array.from(rooms.keys()));
// Get number of sockets in a room
const roomSize = io.sockets.adapter.rooms.get('general')?.size || 0;
console.log('Users in general:', roomSize);
// Check if room exists
const roomExists = io.sockets.adapter.rooms.has('general');
console.log('General room exists?', roomExists);
Practical Room Example: Chat Rooms
// SERVER
const chatRooms = new Map(); // Track room metadata
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Join room
socket.on('joinRoom', async (roomName, username) => {
// Leave current rooms (except socket's own room)
Array.from(socket.rooms).forEach(room => {
if (room !== socket.id) {
socket.leave(room);
}
});
// Join new room
socket.join(roomName);
// Track room metadata
if (!chatRooms.has(roomName)) {
chatRooms.set(roomName, { users: [] });
}
chatRooms.get(roomName).users.push({ id: socket.id, username });
// Send room info to user
const socketsInRoom = await io.in(roomName).fetchSockets();
socket.emit('roomInfo', {
room: roomName,
userCount: socketsInRoom.length,
users: chatRooms.get(roomName).users
});
// Notify room
socket.to(roomName).emit('userJoined', {
userId: socket.id,
username: username
});
console.log(`${username} joined ${roomName}`);
});
// Send message to room
socket.on('roomMessage', (roomName, message) => {
io.to(roomName).emit('roomMessage', {
userId: socket.id,
message: message,
timestamp: new Date()
});
});
// Handle disconnect
socket.on('disconnect', () => {
// Remove from all room metadata
chatRooms.forEach((roomData, roomName) => {
const userIndex = roomData.users.findIndex(u => u.id === socket.id);
if (userIndex !== -1) {
const username = roomData.users[userIndex].username;
roomData.users.splice(userIndex, 1);
// Notify room
io.to(roomName).emit('userLeft', {
userId: socket.id,
username: username
});
}
});
});
});
What Are Namespaces?
Namespaces allow you to split your application logic across multiple communication channels on the same server:
// Think of namespaces like separate Socket.io instances
// Each namespace has its own:
// - Event handlers
// - Rooms
// - Middleware
// - Connected clients
// Examples:
// /chat - chat functionality
// /admin - admin panel
// /notifications - notification system
// /game - game logic
Tip: Use namespaces for different application features (chat vs admin), and use rooms within a namespace to organize users (chat:general vs chat:support).
Creating Namespaces
Define namespaces on the server:
// SERVER
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer);
// Default namespace '/' (already exists)
io.on('connection', (socket) => {
console.log('Connected to default namespace');
});
// Create /chat namespace
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
console.log('Connected to /chat namespace:', socket.id);
socket.on('message', (msg) => {
chatNamespace.emit('message', msg);
});
});
// Create /admin namespace
const adminNamespace = io.of('/admin');
adminNamespace.on('connection', (socket) => {
console.log('Admin connected:', socket.id);
socket.on('adminAction', (action) => {
console.log('Admin action:', action);
});
});
// Create /notifications namespace
const notifNamespace = io.of('/notifications');
notifNamespace.on('connection', (socket) => {
socket.emit('welcome', 'Connected to notifications');
});
httpServer.listen(3000);
Connecting to Namespaces (Client)
// CLIENT
// Connect to default namespace
const socket = io('http://localhost:3000');
// Connect to /chat namespace
const chatSocket = io('http://localhost:3000/chat');
// Connect to /admin namespace
const adminSocket = io('http://localhost:3000/admin');
// Connect to /notifications namespace
const notifSocket = io('http://localhost:3000/notifications');
// Use different namespaces
chatSocket.on('connect', () => {
console.log('Connected to chat');
chatSocket.emit('message', 'Hello from chat!');
});
adminSocket.on('connect', () => {
console.log('Connected to admin');
adminSocket.emit('adminAction', 'view-users');
});
notifSocket.on('welcome', (msg) => {
console.log(msg);
});
Namespace-Specific Events and Rooms
Each namespace has its own rooms and events:
// SERVER
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
// Join rooms within /chat namespace
socket.join('general');
socket.join('tech');
// Broadcast within namespace
chatNamespace.emit('userCount', chatNamespace.sockets.size);
// Broadcast to room within namespace
socket.to('general').emit('userJoined', socket.id);
// Handle events within namespace
socket.on('chatMessage', (room, message) => {
chatNamespace.to(room).emit('chatMessage', {
room: room,
message: message,
sender: socket.id
});
});
});
// Rooms in different namespaces are independent
const adminNamespace = io.of('/admin');
adminNamespace.on('connection', (socket) => {
// This 'general' room is separate from chat's 'general' room
socket.join('general');
});
Dynamic Namespaces
Create namespaces dynamically based on patterns:
// SERVER
// Create namespace for each organization
io.of(/^\/org-\w+$/).on('connection', (socket) => {
const namespace = socket.nsp.name; // e.g., '/org-acme'
console.log(`Connected to ${namespace}`);
socket.on('message', (msg) => {
// Broadcast to this specific organization
socket.nsp.emit('message', msg);
});
});
// CLIENT
const acmeSocket = io('http://localhost:3000/org-acme');
const xyzSocket = io('http://localhost:3000/org-xyz');
Room vs Namespace: When to Use Which?
// Use NAMESPACES for:
// ✓ Separating different application features
// ✓ Different authentication/authorization logic
// ✓ Different middleware per feature
// ✓ Completely separate event handlers
// Example: /chat, /admin, /game
// Use ROOMS for:
// ✓ Grouping users within the same feature
// ✓ Temporary groups (chat rooms, game lobbies)
// ✓ Broadcasting to subsets of users
// ✓ Same event handlers, different recipients
// Example: chat:general, chat:support, game:lobby-1
Complete Example: Chat with Rooms and Namespaces
// SERVER
const io = require('socket.io')(3000);
// /chat namespace with rooms
const chat = io.of('/chat');
chat.on('connection', (socket) => {
console.log('User connected to chat');
// Join default room
socket.join('general');
chat.to('general').emit('notification', 'New user joined general');
// Switch rooms
socket.on('switchRoom', (newRoom) => {
const currentRooms = Array.from(socket.rooms).filter(r => r !== socket.id);
currentRooms.forEach(room => {
socket.leave(room);
chat.to(room).emit('notification', `User left ${room}`);
});
socket.join(newRoom);
chat.to(newRoom).emit('notification', `User joined ${newRoom}`);
socket.emit('roomChanged', newRoom);
});
// Send message to current rooms
socket.on('message', async (message) => {
const rooms = Array.from(socket.rooms).filter(r => r !== socket.id);
rooms.forEach(room => {
chat.to(room).emit('message', {
room: room,
sender: socket.id,
message: message
});
});
});
socket.on('disconnect', () => {
console.log('User disconnected from chat');
});
});
// /admin namespace (no rooms needed)
const admin = io.of('/admin');
admin.on('connection', (socket) => {
console.log('Admin connected');
socket.on('getStats', async () => {
const chatSockets = await chat.fetchSockets();
socket.emit('stats', {
chatUsers: chatSockets.length,
adminUsers: admin.sockets.size
});
});
});
Exercise: Build a multi-room chat system:
- Create a /chat namespace with three rooms: general, tech, and random
- Allow users to join and switch between rooms
- Broadcast join/leave notifications to room members only
- Implement a command to list all users in the current room
- Add an /admin namespace that can see statistics for all rooms
- Ensure messages are only sent to the correct room
- Clean up room metadata when users disconnect