Core Concepts Recap
Building real-time features with Socket.io teaches you fundamentals of event-driven architecture that apply far beyond chat apps. Every system you build that needs live updates — dashboards, collaborative tools, multiplayer games, live notifications — uses these same patterns.
---
Client-Side Socket.io with React
// React component: connecting to Socket.io
import { io, Socket } from 'socket.io-client';
import { useEffect, useRef, useState } from 'react';
function ChatRoom({ roomId, userId, username }: Props) {
const socketRef = useRef<Socket | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [onlineUsers, setOnlineUsers] = useState(0);
useEffect(() => {
// Create socket connection on mount
socketRef.current = io(import.meta.env.VITE_API_BASE_URL, {
withCredentials: true,
});
const socket = socketRef.current;
socket.on('connect', () => {
console.log('Connected:', socket.id);
socket.emit('joinRoom', { roomId, userId, username });
});
socket.on('receiveMessage', (msg: Message) => {
setMessages((prev) => [...prev, msg]);
});
socket.on('onlineUsers', (count: number) => {
setOnlineUsers(count);
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
});
socket.on('connect_error', (err) => {
console.error('Connection error:', err.message);
});
// Cleanup on unmount
return () => {
socket.disconnect();
};
}, [roomId]);
const sendMessage = (text: string) => {
socketRef.current?.emit('sendMessage', { roomId, message: text });
};
return (
<div>
<p>Online: {onlineUsers}</p>
{messages.map((m) => <div key={m.id}>{m.username}: {m.message}</div>)}
</div>
);
}
---
Custom useSocket Hook
Building a reusable useSocket hook keeps socket logic out of UI components:
// hooks/useSocket.ts
import { useEffect, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
export const useSocket = (url: string) => {
const socketRef = useRef<Socket | null>(null);
if (!socketRef.current) {
socketRef.current = io(url, {
withCredentials: true,
autoConnect: false, // Don't connect until we call connect()
});
}
useEffect(() => {
const socket = socketRef.current!;
socket.connect();
return () => {
socket.disconnect();
};
}, []);
return socketRef.current;
};
---
Error Handling
socket.on('connect_error', (err) => {
console.error('Connection failed:', err.message);
// err.message values: 'xhr poll error', 'websocket error', 'timeout'
// Could show a "Reconnecting..." UI
});
socket.on('error', (err) => {
console.error('Socket error:', err);
});
// Manual reconnection:
socket.disconnect();
socket.connect();
---
Acknowledgements
Acknowledgements confirm that a message was received by the server:
// Client: emit with callback
socket.emit('sendMessage', { roomId, message }, (response: { success: boolean; id: string }) => {
if (response.success) {
// Message confirmed saved by server
console.log('Message saved with id:', response.id);
}
});
// Server: call the callback function
socket.on('sendMessage', async ({ roomId, message }, callback) => {
const saved = await Message.create({ roomId, message, sender: socket.data.userId });
io.to(roomId).emit('receiveMessage', saved);
callback({ success: true, id: saved._id.toString() });
});
---
Volatile Events
Volatile events are dropped if the socket is not currently connected:
// Good for non-critical real-time data (typing indicators, cursor positions)
socket.volatile.emit('cursorPosition', { x: 100, y: 200 });
// If disconnected momentarily, this is dropped — doesn't queue up
---
Scaling Socket.io — The Challenge
A single Node.js process has one event loop. If you run multiple Node.js processes (for load balancing or to use multiple CPU cores), each process has its own in-memory state. Socket.io rooms are in-memory — socket in Process 1 cannot send to room that socket in Process 2 joined.
Solution: Redis Adapter
npm install @socket.io/redis-adapter redis
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// Now all processes share room state via Redis pub/sub
---
Real-World Projects Built with Socket.io
| Project | Socket.io Features Used |
|---|---|
| Live Chat App | Rooms, typing indicators, message delivery, online status |
| Collaborative Text Editor | Real-time text sync, cursor positions, user presence |
| Multiplayer Tic-Tac-Toe | Rooms (game sessions), game state sync, turn management |
| Real-Time Dashboard | Broadcasting live metrics, server push (no polling) |
| Push Notifications | Per-user namespaces, broadcast to specific user |
| Live Auction | Bid updates broadcast to all participants in a room |
---
Interview Questions on Socket.io
Q1: What is the difference between WebSocket and Socket.io? WebSocket is the underlying browser API/protocol. Socket.io is a library built on top of WebSocket that adds polling fallback, rooms, namespaces, auto-reconnect, and event system.
Q2: How do you scale Socket.io across multiple servers? Use the Redis adapter (@socket.io/redis-adapter) which uses Redis pub/sub to share events across all instances.
Q3: What are rooms and namespaces? Rooms are arbitrary groups of sockets within a namespace. A socket can join multiple rooms. Namespaces are separate channels on the same server (like having multiple apps on one server).
Q4: What are volatile events? When would you use them? Volatile events are dropped if the socket is temporarily disconnected. Use them for non-critical, high-frequency data like typing indicators or mouse cursor positions where losing occasional updates is acceptable.
Q5: How do you prevent a user from joining someone else's private room? Authenticate the socket connection using JWT and validate room access in the joinRoom event handler before calling socket.join():
socket.on('joinRoom', async ({ roomId }) => {
const hasAccess = await checkRoomAccess(socket.data.userId, roomId);
if (!hasAccess) return socket.emit('error', 'Access denied');
socket.join(roomId);
});
---
Socket.io Event-Driven Architecture Lessons
Learning Socket.io teaches you to think in events rather than request-response:
- Events are named:
'sendMessage','joinRoom','typing' - Both client and server emit and listen to events
- State is maintained in server memory (rooms, user data)
- Side effects happen asynchronously (DB saves, notifications)
These patterns are the foundation for understanding event-driven microservices (Kafka, RabbitMQ) and reactive systems — one of the most sought-after architectural skills in senior backend roles.