Real-TimeWebSockets & Real-Time Data
Standard HTTP is request-response: the client asks, the server answers, and the connection closes. This is perfect for 95% of applications. But some features require the server to push data to the client without being asked — a chat message, a stock price update, a live notification, a collaborative editing cursor. For these, we need WebSockets.
The Problem with HTTP Polling
Before WebSockets, developers solved real-time updates with polling: every few seconds, the client sends a new request asking "any updates?" This works, but it's extremely wasteful:
// ❌ Polling — wasteful approach
setInterval(async () => {
const res = await fetch('/api/messages');
const data = await res.json();
renderMessages(data.messages);
}, 1000); // Hammering the server every second, even when nothing changed
With 10,000 users polling every second, your server is handling 10,000 requests/second even when there's zero new activity. WebSockets solve this by maintaining a persistent open connection that the server can push through at any time.
How WebSockets Work
A WebSocket connection starts as a regular HTTP request with a special Upgrade header. The server agrees to "upgrade" the connection, and from that point, the TCP connection stays open as a full-duplex channel — both sides can send messages at any time, independently, with no request/response cycle.
Client sends
HTTP Upgrade: websocket
→
Server responds
101 Switching Protocols
→
Persistent TCP
connection open
→
Both sides push
messages freely
Socket.io — WebSockets Made Easy
Socket.io is the most popular WebSocket library for Node.js. It wraps the WebSocket protocol with useful abstractions: automatic reconnection, rooms, namespaces, and fallback to long-polling for environments where WebSockets are blocked.
npm install socket.io
// ─── SERVER SIDE ───
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app); // Create HTTP server from Express app
// Attach Socket.io to the HTTP server
const io = new Server(server, {
cors: {
origin: 'http://localhost:3000', // Your frontend URL
methods: ['GET', 'POST']
}
});
// In-memory store for connected users (use Redis in production)
const connectedUsers = new Map();
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
// Event: user identifies themselves after connecting
socket.on('authenticate', (userId) => {
connectedUsers.set(userId, socket.id);
socket.userId = userId;
// Join a personal room (for direct messages)
socket.join(`user:${userId}`);
console.log(`User ${userId} authenticated`);
});
// Event: user sends a chat message
socket.on('send_message', async ({ roomId, text }) => {
const message = await db.saveMessage({
text,
roomId,
senderId: socket.userId
});
// Emit to everyone in the room (including sender)
io.to(`room:${roomId}`).emit('new_message', message);
});
// Event: user joins a chat room
socket.on('join_room', (roomId) => {
socket.join(`room:${roomId}`);
socket.to(`room:${roomId}`).emit('user_joined', {
userId: socket.userId,
timestamp: new Date()
});
});
// Handle disconnect
socket.on('disconnect', () => {
connectedUsers.delete(socket.userId);
console.log(`Client disconnected: ${socket.id}`);
});
});
server.listen(3000, () => console.log('WebSocket server running on port 3000'));
Frontend WebSocket Connection
// ─── CLIENT SIDE (Browser) ───
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000', {
auth: { token: localStorage.getItem('authToken') }
});
// Connection lifecycle events
socket.on('connect', () => console.log('Connected to server'));
socket.on('disconnect', () => console.log('Disconnected from server'));
socket.on('connect_error', (err) => console.error('Connection failed:', err));
// Identify user after connecting
socket.emit('authenticate', currentUserId);
// Join a chat room
socket.emit('join_room', 'room_general');
// Send a message
function sendMessage(text) {
socket.emit('send_message', { roomId: 'room_general', text });
}
// Listen for new messages from the server
socket.on('new_message', (message) => {
appendMessageToUI(message);
});
Server-Sent Events (SSE) — One-Way Push
If you only need the server to push data to the client (not bidirectional communication), Server-Sent Events (SSE) are simpler than WebSockets. SSE uses a regular HTTP connection that stays open, and the server streams data chunks to the client. Browsers have native support with the EventSource API.
SSE is ideal for: live dashboards, notification feeds, progress bars for long-running operations.
// Server: stream live stock prices
app.get('/api/stock-stream', authenticate, (req, res) => {
// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
// Send price updates every second
const interval = setInterval(async () => {
const price = await fetchCurrentStockPrice('VOIDX');
// SSE format: "data:
\n\n"
res.write(`data: ${JSON.stringify({ price, timestamp: Date.now() })}\n\n`);
}, 1000);
// Clean up when client disconnects
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
// Client: connect to the stream
const eventSource = new EventSource('/api/stock-stream');
eventSource.onmessage = (event) => {
const { price } = JSON.parse(event.data);
updatePriceDisplay(price);
};