Real-time features are expected in modern applications - from chat and notifications to collaborative editing. WebSockets provide the foundation for bidirectional communication. This guide covers production patterns for building scalable real-time systems.
WebSocket Server Setup
// WebSocket server with Socket.io
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis';
import { verifyToken } from './auth';
const pubClient = new Redis(process.env.REDIS_URL!);
const subClient = pubClient.duplicate();
const io = new Server({
cors: {
origin: process.env.CLIENT_URL,
credentials: true,
},
adapter: createAdapter(pubClient, subClient),
});
// Authentication middleware
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.data.user = user;
next();
} catch (error) {
next(new Error('Invalid token'));
}
});
// Connection handling
io.on('connection', (socket) => {
const userId = socket.data.user.id;
console.log(`User connected: ${userId}`);
// Join user's personal room
socket.join(`user:${userId}`);
// Track online status
pubClient.sadd('online-users', userId);
io.emit('user:online', { userId });
// Handle joining rooms
socket.on('room:join', async (roomId: string) => {
// Verify user has access to room
const hasAccess = await checkRoomAccess(userId, roomId);
if (!hasAccess) {
socket.emit('error', { message: 'Access denied' });
return;
}
socket.join(`room:${roomId}`);
socket.to(`room:${roomId}`).emit('room:user-joined', {
userId,
roomId,
});
});
// Handle messages
socket.on('message:send', async (data) => {
const message = await saveMessage({
roomId: data.roomId,
userId,
content: data.content,
});
io.to(`room:${data.roomId}`).emit('message:new', message);
});
// Handle disconnection
socket.on('disconnect', () => {
pubClient.srem('online-users', userId);
io.emit('user:offline', { userId });
});
});
io.listen(3001);Client Implementation
// React client with reconnection and state management
import { io, Socket } from 'socket.io-client';
import { create } from 'zustand';
interface SocketState {
socket: Socket | null;
connected: boolean;
connect: (token: string) => void;
disconnect: () => void;
}
export const useSocketStore = create<SocketState>((set, get) => ({
socket: null,
connected: false,
connect: (token: string) => {
const socket = io(process.env.NEXT_PUBLIC_WS_URL!, {
auth: { token },
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
});
socket.on('connect', () => {
console.log('Connected to WebSocket');
set({ connected: true });
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
set({ connected: false });
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error.message);
});
set({ socket });
},
disconnect: () => {
const { socket } = get();
if (socket) {
socket.disconnect();
set({ socket: null, connected: false });
}
},
}));
// Custom hook for real-time messages
function useMessages(roomId: string) {
const socket = useSocketStore((state) => state.socket);
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
if (!socket) return;
// Join room
socket.emit('room:join', roomId);
// Listen for messages
socket.on('message:new', (message: Message) => {
setMessages((prev) => [...prev, message]);
});
// Cleanup
return () => {
socket.off('message:new');
socket.emit('room:leave', roomId);
};
}, [socket, roomId]);
const sendMessage = useCallback((content: string) => {
socket?.emit('message:send', { roomId, content });
}, [socket, roomId]);
return { messages, sendMessage };
}Scaling Strategies
WebSocket Scaling Patterns
Horizontal Scaling:
- Use Redis adapter for multi-server pub/sub
- Implement sticky sessions or socket affinity
- Consider managed solutions (Pusher, Ably)
Performance:
- Use binary protocols (MessagePack) for large payloads
- Implement message batching for high-frequency updates
- Use room-based broadcasting instead of individual emissions
Reliability:
- Implement heartbeat/ping-pong for connection health
- Add message acknowledgments for critical events
- Use message queues for guaranteed delivery
Conclusion
WebSockets enable powerful real-time features when implemented correctly. Focus on proper authentication, connection management, and scaling patterns from the start.
Need help building real-time features? Contact Jishu Labs for expert backend development consulting.
About Michael Torres
Michael Torres is the Backend Lead at Jishu Labs with expertise in real-time systems and distributed architectures.