Module 06: WebSocket & Real-Time Communication
Goal: Understand WebSocket protocol, build real-time features, and learn Socket.IO. Time: 2 days of focused study Prerequisites: Module 01-04
Table of Contents
- The Problem with HTTP for Real-Time
- What is WebSocket?
- WebSocket Protocol — Under the Hood
- The
wsLibrary — Raw WebSocket - Socket.IO — Feature-Rich WebSocket
- Real-World Patterns
- Authentication with WebSocket
- Scaling WebSocket Servers
- Server-Sent Events (SSE) — Alternative
- Practice: Build a Chat App
- Interview Questions
1. The Problem with HTTP
HTTP is a request-response protocol — the client asks, the server responds. The server can NEVER initiate communication.
HTTP (Half-Duplex):
Client ──── Request ───► Server
Client ◄─── Response ─── Server
Problem: If something happens on the server, the client doesn't know until it asks.
Workarounds (all have problems):
1. Short Polling (dumb):
Client asks every 1 second: "Any updates?"
Server: "No." "No." "No." "Yes! Here's the update."
Problem: Wastes bandwidth, high latency, 90% of requests return nothing
2. Long Polling (smarter):
Client asks: "Any updates?"
Server holds the connection open until an update is available (or timeout)
Client immediately asks again after getting a response.
Problem: Each response requires a new HTTP connection, HTTP overhead per message
3. Server-Sent Events (good for one-way):
Server pushes events to client over a long-lived HTTP connection
Problem: One-directional (server → client only), no binary data2. What is WebSocket?
WebSocket is a full-duplex protocol — both client and server can send messages at any time, independently.
WebSocket (Full-Duplex):
Client ◄──── Messages ────► Server
✅ Both sides can send at any time
✅ Single persistent TCP connection
✅ Very low overhead per message (2-14 bytes vs 100+ for HTTP)
✅ Real-time bidirectional communicationHTTP vs WebSocket Comparison
Feature │ HTTP │ WebSocket
─────────────────┼───────────────────┼──────────────────
Direction │ Client → Server │ Both directions
Connection │ New per request │ Persistent
Overhead │ ~100 bytes/msg │ ~2-14 bytes/msg
State │ Stateless │ Stateful
Best for │ CRUD, REST APIs │ Real-time apps
Protocol │ http:// / https://│ ws:// / wss://3. WebSocket Protocol
The Handshake
WebSocket starts with an HTTP upgrade request:
Client → Server (HTTP Upgrade Request):
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket ← "I want to upgrade to WebSocket"
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZQ== ← Random base64 string
Sec-WebSocket-Version: 13
Server → Client (101 Switching Protocols):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ← Proof of receipt
After this handshake, the connection is upgraded from HTTP to WebSocket.
The TCP connection stays open for bidirectional messaging.Frame Format
WebSocket messages are sent in "frames":
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+-------------------------------+
| Payload Data |
+---------------------------------------------------------------+
Opcodes:
0x1 = text frame
0x2 = binary frame
0x8 = close
0x9 = ping
0xA = pong
Overhead comparison:
HTTP: ~100-800 bytes per request (headers)
WebSocket: ~2-14 bytes per frame (just the frame header)4. The ws Library — Raw WebSocket
ws is a simple, fast WebSocket library for Node.js.
npm install wsBasic Server
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => {
console.log('Client connected from:', req.socket.remoteAddress);
// Send message to client
ws.send(JSON.stringify({ type: 'welcome', message: 'Hello!' }));
// Receive messages from client
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('Received:', message);
// Echo back
ws.send(JSON.stringify({ type: 'echo', data: message }));
});
// Client disconnected
ws.on('close', (code, reason) => {
console.log(`Client disconnected: ${code} - ${reason}`);
});
// Error handling
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
// Ping/Pong for connection health check
ws.on('pong', () => {
ws.isAlive = true;
});
});
// Health check interval — detect dead connections
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
console.log('Terminating dead connection');
return ws.terminate();
}
ws.isAlive = false;
ws.ping(); // Client auto-responds with pong
});
}, 30000); // Check every 30 seconds
wss.on('close', () => clearInterval(interval));
console.log('WebSocket server running on ws://localhost:8080');Basic Client (Node.js)
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
console.log('Connected to server');
ws.send(JSON.stringify({ type: 'chat', content: 'Hello, server!' }));
});
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('From server:', message);
});
ws.on('close', () => console.log('Disconnected'));
ws.on('error', (err) => console.error('Error:', err));Basic Client (Browser)
// Browser has built-in WebSocket API — no library needed!
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'chat', content: 'Hello!' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
ws.onclose = (event) => {
console.log(`Disconnected: ${event.code} ${event.reason}`);
};
ws.onerror = (error) => {
console.error('Error:', error);
};Broadcasting to All Clients
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
// Broadcast to ALL connected clients
function broadcast(data, exclude = null) {
const message = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client !== exclude && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message.type === 'chat') {
// Broadcast to all OTHER clients
broadcast({
type: 'chat',
from: message.from,
content: message.content,
timestamp: Date.now(),
}, ws); // Exclude sender
}
});
});Integrating with Express
const express = require('express');
const http = require('http');
const { WebSocketServer } = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server }); // Share the same server!
// Express routes
app.get('/api/status', (req, res) => {
res.json({
status: 'ok',
connections: wss.clients.size,
});
});
// WebSocket connections
wss.on('connection', (ws, req) => {
// req.url can be used for routing: ws://localhost:3000/chat
console.log(`WebSocket connection to ${req.url}`);
ws.on('message', (data) => {
// Handle messages
});
});
server.listen(3000, () => {
console.log('HTTP + WebSocket server on port 3000');
});5. Socket.IO — Feature-Rich WebSocket
Socket.IO adds reliability features on top of WebSocket:
Feature │ Raw WebSocket │ Socket.IO
──────────────────┼───────────────┼──────────────────
Auto-reconnection │ ❌ Manual │ ✅ Built-in
Rooms/namespaces │ ❌ Manual │ ✅ Built-in
Broadcasting │ ❌ Manual │ ✅ Built-in
Fallbacks │ ❌ WebSocket │ ✅ HTTP long-polling
Acknowledgements │ ❌ Manual │ ✅ Built-in
Binary support │ ✅ Yes │ ✅ Yes
Middleware │ ❌ No │ ✅ YesServer Setup
// npm install socket.io
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST'],
credentials: true,
},
pingTimeout: 60000,
pingInterval: 25000,
});
// Connection event
io.on('connection', (socket) => {
console.log(`User connected: ${socket.id}`);
// Listen for events from this client
socket.on('chat:message', (data) => {
console.log('Message:', data);
// Emit to ALL clients (including sender)
io.emit('chat:message', {
...data,
id: Date.now(),
timestamp: new Date(),
});
});
// Emit to all EXCEPT sender
socket.on('user:typing', (data) => {
socket.broadcast.emit('user:typing', data);
});
// Acknowledgement — client confirms receipt
socket.on('message:read', (data, callback) => {
// Process the event
markMessageAsRead(data.messageId);
// Send acknowledgement back to client
callback({ status: 'ok', readAt: new Date() });
});
// Disconnect
socket.on('disconnect', (reason) => {
console.log(`User disconnected: ${socket.id} — ${reason}`);
});
});
server.listen(3000);Client Setup
// npm install socket.io-client
const { io } = require('socket.io-client');
// Or in browser: <script src="/socket.io/socket.io.js"></script>
const socket = io('http://localhost:3000', {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
auth: {
token: 'user-jwt-token', // Send auth data on connection
},
});
socket.on('connect', () => {
console.log('Connected:', socket.id);
});
socket.on('chat:message', (message) => {
console.log('New message:', message);
});
// Send message
socket.emit('chat:message', {
content: 'Hello!',
room: 'general',
});
// Send with acknowledgement
socket.emit('message:read', { messageId: 123 }, (response) => {
console.log('Server acknowledged:', response);
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});Rooms — Grouping Clients
io.on('connection', (socket) => {
// Join a room
socket.on('room:join', (roomName) => {
socket.join(roomName);
console.log(`${socket.id} joined room: ${roomName}`);
// Notify others in the room
socket.to(roomName).emit('room:userJoined', {
userId: socket.id,
room: roomName,
});
});
// Leave a room
socket.on('room:leave', (roomName) => {
socket.leave(roomName);
socket.to(roomName).emit('room:userLeft', { userId: socket.id });
});
// Send message to a specific room
socket.on('room:message', ({ room, content }) => {
io.to(room).emit('chat:message', {
from: socket.id,
content,
room,
timestamp: Date.now(),
});
});
// Emit to a specific user
socket.on('private:message', ({ targetId, content }) => {
io.to(targetId).emit('private:message', {
from: socket.id,
content,
});
});
});
// Emitting cheatsheet:
// io.emit(event, data) → ALL clients
// socket.emit(event, data) → ONLY this client
// socket.broadcast.emit(event, data) → ALL except this client
// io.to('room').emit(event, data) → ALL in room
// socket.to('room').emit(event, data) → ALL in room except sender
// io.to(socketId).emit(event, data) → Specific clientNamespaces — Separate Communication Channels
// Default namespace is '/'
const io = new Server(server);
// Custom namespaces
const chatNsp = io.of('/chat');
const adminNsp = io.of('/admin');
const notificationsNsp = io.of('/notifications');
chatNsp.on('connection', (socket) => {
console.log('User connected to /chat');
// Only handles chat-related events
});
adminNsp.on('connection', (socket) => {
// Verify admin access
if (socket.handshake.auth.role !== 'admin') {
socket.disconnect();
return;
}
console.log('Admin connected to /admin');
});
notificationsNsp.on('connection', (socket) => {
console.log('User connected to /notifications');
});
// Client connects to specific namespace:
const chatSocket = io('http://localhost:3000/chat');
const adminSocket = io('http://localhost:3000/admin', {
auth: { role: 'admin', token: 'xxx' }
});Socket.IO Middleware
// Authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication required'));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.user = decoded; // Attach user info to socket
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
// Logging middleware
io.use((socket, next) => {
console.log(`Connection attempt from: ${socket.handshake.address}`);
next();
});
// Per-namespace middleware
chatNsp.use(chatAuthMiddleware);
// Handle middleware errors on client:
socket.on('connect_error', (err) => {
console.log('Connection error:', err.message);
// "Authentication required" or "Invalid token"
});6. Real-World Patterns
1. Presence System (Online/Offline Status)
const onlineUsers = new Map(); // userId → Set of socketIds
io.on('connection', (socket) => {
const userId = socket.user.id;
// Track online status
if (!onlineUsers.has(userId)) {
onlineUsers.set(userId, new Set());
}
onlineUsers.get(userId).add(socket.id);
// Notify others that user is online
socket.broadcast.emit('user:online', { userId });
// Send current online users list
socket.emit('users:online', Array.from(onlineUsers.keys()));
socket.on('disconnect', () => {
const userSockets = onlineUsers.get(userId);
userSockets?.delete(socket.id);
// Only mark as offline if no more connections (user might have multiple tabs)
if (!userSockets || userSockets.size === 0) {
onlineUsers.delete(userId);
socket.broadcast.emit('user:offline', { userId });
}
});
});2. Typing Indicator
io.on('connection', (socket) => {
let typingTimeout;
socket.on('typing:start', ({ room }) => {
socket.to(room).emit('typing:start', {
userId: socket.user.id,
name: socket.user.name,
});
// Auto-stop after 3 seconds of no typing events
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
socket.to(room).emit('typing:stop', { userId: socket.user.id });
}, 3000);
});
socket.on('typing:stop', ({ room }) => {
clearTimeout(typingTimeout);
socket.to(room).emit('typing:stop', { userId: socket.user.id });
});
});3. Real-Time Notifications
// Send notification to specific user from anywhere in your app
function sendNotification(userId, notification) {
const userSockets = onlineUsers.get(userId);
if (userSockets) {
userSockets.forEach(socketId => {
io.to(socketId).emit('notification', {
id: crypto.randomUUID(),
...notification,
createdAt: new Date(),
read: false,
});
});
}
// Also save to database for offline users
saveNotificationToDB(userId, notification);
}
// Usage from Express route:
app.post('/api/orders', async (req, res) => {
const order = await Order.create(req.body);
// Send real-time notification
sendNotification(order.userId, {
type: 'order_created',
title: 'Order Confirmed',
message: `Your order #${order.id} has been placed.`,
});
res.status(201).json(order);
});4. Live Collaborative Editing
io.on('connection', (socket) => {
socket.on('document:join', (docId) => {
socket.join(`doc:${docId}`);
// Send current document state to new user
const doc = documents.get(docId);
socket.emit('document:state', doc);
});
socket.on('document:change', ({ docId, change }) => {
// Apply change to document
applyChange(docId, change);
// Broadcast change to all OTHER editors
socket.to(`doc:${docId}`).emit('document:change', {
change,
userId: socket.user.id,
timestamp: Date.now(),
});
});
socket.on('cursor:move', ({ docId, position }) => {
socket.to(`doc:${docId}`).emit('cursor:move', {
userId: socket.user.id,
position,
});
});
});7. Authentication with WebSocket
With ws Library
const { WebSocketServer } = require('ws');
const jwt = require('jsonwebtoken');
const url = require('url');
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, req) => {
// Method 1: Token in query string
// ws://localhost:8080?token=eyJhbG...
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
const token = parsedUrl.searchParams.get('token');
// Method 2: Token in custom header (from Node.js clients)
// const token = req.headers['authorization']?.split(' ')[1];
// Method 3: Token in cookie
// const cookies = parseCookies(req.headers.cookie);
// const token = cookies.accessToken;
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
ws.user = user;
ws.send(JSON.stringify({ type: 'auth:success', user }));
} catch (err) {
ws.send(JSON.stringify({ type: 'auth:failed', error: 'Invalid token' }));
ws.close(4001, 'Unauthorized');
return;
}
ws.on('message', (data) => {
console.log(`Message from ${ws.user.name}:`, data.toString());
});
});Verify on Upgrade (Before Connection Established)
const server = http.createServer(app);
const wss = new WebSocketServer({ noServer: true });
// Handle upgrade manually — verify BEFORE accepting connection
server.on('upgrade', (request, socket, head) => {
const token = new URL(request.url, 'http://localhost').searchParams.get('token');
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
wss.handleUpgrade(request, socket, head, (ws) => {
ws.user = user;
wss.emit('connection', ws, request);
});
} catch (err) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
}
});8. Scaling WebSocket Servers
The Problem
With 1 server, broadcasting works fine:
Server 1: [User A, User B, User C]
broadcast() → sends to A, B, C ✅
With multiple servers (load balanced), broadcasting breaks:
Server 1: [User A, User B]
Server 2: [User C, User D]
User A sends message on Server 1
broadcast() on Server 1 → sends to A, B only ❌
User C and D on Server 2 never get the message!Solution: Redis Adapter (Pub/Sub)
// npm install @socket.io/redis-adapter redis
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// Now io.emit() and socket.to('room').emit() work across ALL servers!
// The Redis adapter uses pub/sub to relay messages between servers.
/*
Architecture:
User A ──► Server 1 ──┐
User B ──► Server 1 │
├──► Redis (Pub/Sub) ──► All Servers
User C ──► Server 2 │
User D ──► Server 2 ──┘
When Server 1 emits a message:
1. Server 1 sends to its local clients (A, B)
2. Server 1 publishes to Redis
3. Redis broadcasts to all other servers
4. Server 2 receives from Redis and sends to its clients (C, D)
*/Sticky Sessions for WebSocket
Problem: Socket.IO uses HTTP long-polling initially, then upgrades to WebSocket.
The initial HTTP requests and the upgrade MUST hit the same server.
Solution: Sticky sessions — ensure all requests from one client go to one server.
Nginx config:
upstream io_nodes {
ip_hash; # Sticky sessions based on IP
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
location /socket.io/ {
proxy_pass http://io_nodes;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}9. Server-Sent Events (SSE)
SSE is a simpler alternative when you only need server → client communication.
// Server
app.get('/api/events', (req, res) => {
// Set SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
// Send events
const sendEvent = (event, data) => {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Send initial data
sendEvent('connected', { message: 'Connected to SSE' });
// Send periodic updates
const interval = setInterval(() => {
sendEvent('heartbeat', { timestamp: Date.now() });
}, 5000);
// Listen for new data (e.g., from database changes)
const handler = (notification) => {
sendEvent('notification', notification);
};
eventBus.on('notification', handler);
// Cleanup on disconnect
req.on('close', () => {
clearInterval(interval);
eventBus.off('notification', handler);
});
});
// Client (Browser — built-in API)
const source = new EventSource('/api/events');
source.addEventListener('notification', (event) => {
const data = JSON.parse(event.data);
console.log('Notification:', data);
});
source.addEventListener('heartbeat', (event) => {
console.log('Heartbeat:', JSON.parse(event.data));
});
source.onerror = () => {
console.log('SSE connection error — will auto-reconnect');
};When to Use What
SSE (Server-Sent Events):
✅ Server → Client only (notifications, live feeds, stock prices)
✅ Auto-reconnection built-in
✅ Simpler than WebSocket
✅ Works over HTTP (no protocol upgrade)
❌ One-directional only
❌ No binary data
WebSocket:
✅ Bidirectional (chat, games, collaboration)
✅ Binary data support
✅ Lower latency
❌ More complex
❌ Need manual reconnection (or Socket.IO)
Long Polling:
✅ Works everywhere (even behind strict firewalls)
❌ Higher latency
❌ More server resources
❌ Complex to implement well10. Practice: Build a Chat App
// Complete chat application with rooms, typing indicators, and history
// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const jwt = require('jsonwebtoken');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: '*' }
});
// In-memory storage (use database in production)
const messages = new Map(); // room → messages[]
const onlineUsers = new Map(); // userId → { socketId, name, room }
// Auth middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
socket.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
io.on('connection', (socket) => {
console.log(`${socket.user.name} connected`);
// ---- JOIN ROOM ----
socket.on('room:join', (room) => {
// Leave previous room
const prevRoom = onlineUsers.get(socket.user.id)?.room;
if (prevRoom) {
socket.leave(prevRoom);
socket.to(prevRoom).emit('user:left', { name: socket.user.name });
}
// Join new room
socket.join(room);
onlineUsers.set(socket.user.id, {
socketId: socket.id,
name: socket.user.name,
room,
});
// Send room history
const history = messages.get(room) || [];
socket.emit('room:history', history.slice(-50)); // Last 50 messages
// Notify room
socket.to(room).emit('user:joined', { name: socket.user.name });
// Send online users in room
const roomUsers = Array.from(onlineUsers.values())
.filter(u => u.room === room)
.map(u => u.name);
io.to(room).emit('room:users', roomUsers);
});
// ---- SEND MESSAGE ----
socket.on('message:send', ({ content }, callback) => {
const userInfo = onlineUsers.get(socket.user.id);
if (!userInfo?.room) return;
const message = {
id: Date.now().toString(),
userId: socket.user.id,
name: socket.user.name,
content,
room: userInfo.room,
timestamp: new Date().toISOString(),
};
// Store message
if (!messages.has(userInfo.room)) messages.set(userInfo.room, []);
messages.get(userInfo.room).push(message);
// Broadcast to room
io.to(userInfo.room).emit('message:new', message);
// Acknowledge
callback?.({ status: 'sent', id: message.id });
});
// ---- TYPING ----
socket.on('typing:start', () => {
const userInfo = onlineUsers.get(socket.user.id);
if (userInfo?.room) {
socket.to(userInfo.room).emit('typing:update', {
userId: socket.user.id,
name: socket.user.name,
isTyping: true,
});
}
});
socket.on('typing:stop', () => {
const userInfo = onlineUsers.get(socket.user.id);
if (userInfo?.room) {
socket.to(userInfo.room).emit('typing:update', {
userId: socket.user.id,
name: socket.user.name,
isTyping: false,
});
}
});
// ---- DISCONNECT ----
socket.on('disconnect', () => {
const userInfo = onlineUsers.get(socket.user.id);
if (userInfo?.room) {
socket.to(userInfo.room).emit('user:left', { name: socket.user.name });
// Update room users list
onlineUsers.delete(socket.user.id);
const roomUsers = Array.from(onlineUsers.values())
.filter(u => u.room === userInfo.room)
.map(u => u.name);
io.to(userInfo.room).emit('room:users', roomUsers);
}
});
});
server.listen(3000, () => console.log('Chat server on :3000'));11. Interview Questions
Q1: What is WebSocket? How is it different from HTTP?
Answer: WebSocket is a full-duplex communication protocol that provides persistent, bidirectional communication over a single TCP connection. Unlike HTTP (request-response, new connection each time), WebSocket maintains an open connection where both client and server can send messages at any time. It starts with an HTTP upgrade handshake, then switches to the WebSocket protocol with much lower overhead per message (2-14 bytes vs 100+ for HTTP).
Q2: When would you use WebSocket vs Server-Sent Events vs Long Polling?
Answer: WebSocket for bidirectional real-time (chat, games, collaboration). SSE for server-to-client only (notifications, live feeds) — simpler, auto-reconnects, works over HTTP. Long polling as a fallback when WebSocket is blocked (corporate firewalls) — higher latency and resource usage.
Q3: How would you scale a WebSocket server?
Answer: Use a Redis adapter (pub/sub) to relay messages between server instances. Configure sticky sessions at the load balancer (Nginx ip_hash) so Socket.IO's HTTP polling requests reach the same server. Each server handles its local connections and publishes events to Redis; other servers subscribe and forward to their local clients.
Q4: How do you handle authentication with WebSocket?
Answer: Send JWT in the connection handshake (query parameter, auth object, or cookie). Verify the token before accepting the connection (using the upgrade event or Socket.IO middleware). For the ws library, verify in the connection handler or intercept the HTTP upgrade event. For Socket.IO, use io.use() middleware.
Q5: What are WebSocket rooms?
Answer: Rooms are a server-side concept (Socket.IO) for grouping sockets. A socket can join multiple rooms, and you can emit events to all sockets in a room. Use cases: chat rooms, game lobbies, document collaboration, sending updates to users subscribed to specific topics.
Next Module: 07 - Redis — Caching, pub/sub, rate limiting, and real-time data storage.