Socket.io
Build real-time applications with Socket.io - rooms, namespaces, and production patterns.
5 min read
Why Socket.io?#
Socket.io adds features on top of WebSockets:
- Automatic reconnection - Handles disconnects gracefully
- Rooms and namespaces - Organize connections
- Fallbacks - Works even if WebSockets blocked
- Acknowledgements - Know when messages received
- Broadcasting - Send to multiple clients easily
Installation#
bash
npm install socket.io # Server
npm install socket.io-client # Client (if needed)
Basic Setup#
Server#
javascript
// src/index.js
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST'],
},
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Listen for events
socket.on('chat message', (msg) => {
console.log('Message:', msg);
// Broadcast to all clients
io.emit('chat message', msg);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
httpServer.listen(3000, () => {
console.log('Server running on port 3000');
});
Client#
javascript
// client.js (or React component)
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');
socket.on('connect', () => {
console.log('Connected:', socket.id);
});
socket.on('chat message', (msg) => {
console.log('Received:', msg);
});
// Send message
socket.emit('chat message', 'Hello everyone!');
Rooms#
Group sockets together to broadcast to specific groups:
javascript
// Server
io.on('connection', (socket) => {
// Join a room
socket.on('join room', (roomId) => {
socket.join(roomId);
console.log(`${socket.id} joined room ${roomId}`);
// Notify others in room
socket.to(roomId).emit('user joined', {
userId: socket.id,
roomId,
});
});
// Leave a room
socket.on('leave room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user left', { userId: socket.id });
});
// Send message to room
socket.on('room message', ({ roomId, message }) => {
io.to(roomId).emit('room message', {
from: socket.id,
message,
timestamp: new Date(),
});
});
});
// Client
socket.emit('join room', 'room-123');
socket.emit('room message', { roomId: 'room-123', message: 'Hello room!' });
Namespaces#
Separate concerns with different endpoints:
javascript
// Server
const chatNamespace = io.of('/chat');
const notificationsNamespace = io.of('/notifications');
chatNamespace.on('connection', (socket) => {
console.log('User connected to chat');
socket.on('message', (data) => {
chatNamespace.emit('message', data);
});
});
notificationsNamespace.on('connection', (socket) => {
console.log('User connected to notifications');
// Send notifications only to this namespace
socket.emit('notification', { text: 'Welcome!' });
});
// Client
const chatSocket = io('http://localhost:3000/chat');
const notifSocket = io('http://localhost:3000/notifications');
chatSocket.emit('message', 'Hello chat!');
Authentication#
With Middleware#
javascript
// Server
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication required'));
}
try {
const user = await verifyToken(token);
socket.user = user; // Attach user to socket
next();
} catch (error) {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
console.log('Authenticated user:', socket.user.email);
});
// Client
const socket = io('http://localhost:3000', {
auth: {
token: 'your-jwt-token',
},
});
socket.on('connect_error', (error) => {
console.error('Auth failed:', error.message);
});
Acknowledgements#
Confirm message delivery:
javascript
// Server
socket.on('create order', (orderData, callback) => {
try {
const order = await OrderService.create(orderData);
callback({ success: true, order });
} catch (error) {
callback({ success: false, error: error.message });
}
});
// Client
socket.emit('create order', { productId: '123' }, (response) => {
if (response.success) {
console.log('Order created:', response.order);
} else {
console.error('Failed:', response.error);
}
});
Broadcasting Patterns#
javascript
// To all connected clients
io.emit('announcement', 'Server restarting in 5 minutes');
// To all except sender
socket.broadcast.emit('user typing', { userId: socket.id });
// To specific room
io.to('room-123').emit('room update', data);
// To multiple rooms
io.to('room-1').to('room-2').emit('multi-room', data);
// To specific socket
io.to(socketId).emit('private message', data);
// To all in room except sender
socket.to('room-123').emit('new message', data);
Chat Application Example#
javascript
// Server
const users = new Map(); // socketId -> user info
io.on('connection', (socket) => {
// User joins
socket.on('join', ({ username, room }) => {
users.set(socket.id, { username, room });
socket.join(room);
// Welcome message
socket.emit('message', {
type: 'system',
text: `Welcome to ${room}!`,
});
// Notify others
socket.to(room).emit('message', {
type: 'system',
text: `${username} joined the room`,
});
// Send user list
io.to(room).emit('users', getUsersInRoom(room));
});
// Chat message
socket.on('chat', (text) => {
const user = users.get(socket.id);
if (!user) return;
io.to(user.room).emit('message', {
type: 'chat',
username: user.username,
text,
timestamp: new Date(),
});
});
// Typing indicator
socket.on('typing', () => {
const user = users.get(socket.id);
if (user) {
socket.to(user.room).emit('typing', user.username);
}
});
// Disconnect
socket.on('disconnect', () => {
const user = users.get(socket.id);
if (user) {
io.to(user.room).emit('message', {
type: 'system',
text: `${user.username} left the room`,
});
io.to(user.room).emit('users', getUsersInRoom(user.room));
users.delete(socket.id);
}
});
});
function getUsersInRoom(room) {
return Array.from(users.entries())
.filter(([_, user]) => user.room === room)
.map(([id, user]) => ({ id, username: user.username }));
}
Scaling with Redis#
For multiple servers, use Redis adapter:
bash
npm install @socket.io/redis-adapter redis
javascript
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
const io = new Server(httpServer);
io.adapter(createAdapter(pubClient, subClient));
// Now messages broadcast across all servers!
io.emit('global', 'This reaches all connected clients');
Error Handling#
javascript
io.on('connection', (socket) => {
// Catch errors in event handlers
socket.on('action', async (data) => {
try {
const result = await processAction(data);
socket.emit('action:success', result);
} catch (error) {
socket.emit('action:error', { message: error.message });
}
});
});
// Global error handler
io.engine.on('connection_error', (err) => {
console.error('Connection error:', err);
});
Key Takeaways#
- Rooms for groups - Chat rooms, game lobbies
- Namespaces for concerns - Separate chat from notifications
- Always authenticate - Use middleware for auth
- Acknowledgements for confirmation - Know when messages received
- Redis for scaling - Required for multiple servers
Continue Learning
Ready to level up your skills?
Explore more guides and tutorials to deepen your understanding and become a better developer.