HTTP vs WebSocket
The fundamental limitation of HTTP for real-time features is that it is request-response — the client must initiate every interaction. The server cannot push data to the client spontaneously. If you want real-time updates (new messages, live notifications, collaborative changes), you have to either:
- Short polling: Client requests every 2-3 seconds → wasteful, high server load
- Long polling: Client makes request, server holds it open until data arrives → complex, resource-intensive
- WebSocket: Full-duplex persistent connection → efficient, truly real-time
WebSocket establishes a persistent TCP connection. Once opened, either side (client or server) can send messages at any time without a new HTTP request.
---
HTTP vs WebSocket Comparison
| Feature | HTTP | WebSocket |
|---|---|---|
| Connection type | New connection per request | Single persistent connection |
| Communication | Request-response only | Full-duplex (both sides can send) |
| Overhead | HTTP headers on every request | Minimal per-message overhead |
| Real-time | No (polling required) | Yes |
| Use cases | REST APIs, file downloads | Chat, gaming, live notifications |
| Scalability | Easy (stateless) | Complex (stateful, sticky sessions) |
---
Socket.io Architecture
Socket.io is a library built on top of WebSocket that provides:
- Polling fallback: If WebSocket is blocked (corporate firewalls), falls back to HTTP long polling
- Rooms: Group sockets for targeted messaging
- Namespaces: Separate communication channels within one server
- Auto-reconnection: Automatically tries to reconnect on disconnect
- Event system: Named events for structured communication
npm install socket.io # Server
npm install socket.io-client # Client (React)
---
Server Setup
import { createServer } from 'http';
import { Server } from 'socket.io';
import app from './app.js';
const httpServer = createServer(app); // Wrap Express app in HTTP server
const io = new Server(httpServer, {
cors: {
origin: process.env.CORS_ORIGIN,
methods: ['GET', 'POST'],
credentials: true,
},
pingTimeout: 60000, // Disconnect if no pong received in 60s
pingInterval: 25000, // Send ping every 25s to keep connection alive
});
io.on('connection', (socket) => {
console.log(`New connection: ${socket.id}`);
// socket.id is a unique ID for each connected client
socket.on('disconnect', (reason) => {
console.log(`Disconnected: ${socket.id} (${reason})`);
});
});
httpServer.listen(5000);
---
Emitting Events
| Method | Sends To |
|---|---|
socket.emit('event', data) | Only this client |
io.emit('event', data) | All connected clients |
socket.broadcast.emit('event', data) | All clients EXCEPT this socket |
io.to('roomName').emit('event', data) | All clients in a room |
socket.to('roomName').emit('event', data) | All in room EXCEPT this socket |
---
Rooms
Rooms let you group sockets — perfect for chat rooms, game lobbies, or per-user notifications:
// Join a room
socket.join('room-123');
// Leave a room
socket.leave('room-123');
// Send to everyone in the room
io.to('room-123').emit('message', { text: 'Hello room!' });
// Get all sockets in a room:
const sockets = await io.in('room-123').fetchSockets();
---
Real-Time Chat App Example
io.on('connection', (socket) => {
// User joins a chat room
socket.on('joinRoom', ({ roomId, userId, username }) => {
socket.join(roomId);
socket.data.userId = userId;
socket.data.username = username;
// Notify others in the room
socket.to(roomId).emit('userJoined', {
userId,
username,
message: `${username} joined the chat`,
});
});
// User sends a message
socket.on('sendMessage', async ({ roomId, message }) => {
const msgData = {
id: Date.now(),
userId: socket.data.userId,
username: socket.data.username,
message,
timestamp: new Date().toISOString(),
};
// Save to DB (async, don't block the emit)
// await Message.create(msgData);
// Send to ALL in the room (including sender)
io.to(roomId).emit('receiveMessage', msgData);
});
// User starts typing
socket.on('typing', ({ roomId }) => {
socket.to(roomId).emit('userTyping', { username: socket.data.username });
});
// User disconnects
socket.on('disconnect', () => {
// Socket automatically leaves all rooms on disconnect
console.log(`${socket.data.username} disconnected`);
});
});
---
Connection/Disconnect Events
// Server-side
io.on('connection', (socket) => {
console.log(`Connected: ${socket.id}`);
socket.on('disconnect', (reason) => {
// reason: 'transport close', 'transport error', 'server namespace disconnect', etc.
console.log(`Disconnected: ${reason}`);
});
socket.on('error', (err) => {
console.error(`Socket error: ${err.message}`);
});
});
---
Heartbeat / Ping-Pong
Socket.io automatically maintains the connection with a ping-pong mechanism:
- Server sends
pingeverypingIntervalmilliseconds - Client responds with
pong - If server doesn't receive
pongwithinpingTimeoutmilliseconds, the socket is disconnected - This detects dead connections (e.g., user's internet dropped without proper close event)
---
Namespaces
Namespaces create separate communication channels on the same server:
const chatNS = io.of('/chat'); // Chat namespace
const notificationsNS = io.of('/notifications'); // Notifications namespace
chatNS.on('connection', (socket) => { /* chat logic */ });
notificationsNS.on('connection', (socket) => { /* notification logic */ });
// Client connects to specific namespace:
// const chatSocket = io('http://localhost:5000/chat');