Real-Time Features
Overview
Implement real-time bidirectional communication between clients and servers for instant data synchronization and live updates.
When to Use
-
Chat and messaging applications
-
Live dashboards and analytics
-
Collaborative editing (Google Docs-style)
-
Real-time notifications
-
Live sports scores or stock tickers
-
Multiplayer games
-
Live auctions or bidding systems
-
IoT device monitoring
-
Real-time location tracking
Technologies Comparison
Technology Direction Use Case Browser Support
WebSockets Bidirectional Chat, gaming, collaboration Excellent
SSE Server → Client Live updates, notifications Good (no IE)
Long Polling Request/Response Fallback, simple updates Universal
WebRTC Peer-to-peer Video/audio streaming Good
Implementation Examples
- WebSocket Server (Node.js)
// server.ts import WebSocket, { WebSocketServer } from 'ws'; import { createServer } from 'http';
interface Message { type: 'join' | 'message' | 'leave' | 'typing'; userId: string; username: string; content?: string; timestamp: number; }
interface Client { ws: WebSocket; userId: string; username: string; roomId: string; }
class ChatServer { private wss: WebSocketServer; private clients: Map<string, Client> = new Map(); private rooms: Map<string, Set<string>> = new Map();
constructor(port: number) { const server = createServer(); this.wss = new WebSocketServer({ server });
this.wss.on('connection', this.handleConnection.bind(this));
server.listen(port, () => {
console.log(`WebSocket server running on port ${port}`);
});
// Heartbeat to detect disconnections
this.startHeartbeat();
}
private handleConnection(ws: WebSocket): void { const clientId = this.generateId();
console.log(`New connection: ${clientId}`);
ws.on('message', (data: string) => {
try {
const message: Message = JSON.parse(data.toString());
this.handleMessage(clientId, message, ws);
} catch (error) {
console.error('Invalid message format:', error);
}
});
ws.on('close', () => {
this.handleDisconnect(clientId);
});
ws.on('error', (error) => {
console.error(`WebSocket error for ${clientId}:`, error);
});
// Keep connection alive
(ws as any).isAlive = true;
ws.on('pong', () => {
(ws as any).isAlive = true;
});
}
private handleMessage( clientId: string, message: Message, ws: WebSocket ): void { switch (message.type) { case 'join': this.handleJoin(clientId, message, ws); break;
case 'message':
this.broadcastToRoom(clientId, message);
break;
case 'typing':
this.broadcastToRoom(clientId, message, [clientId]);
break;
case 'leave':
this.handleDisconnect(clientId);
break;
}
}
private handleJoin( clientId: string, message: Message, ws: WebSocket ): void { const client: Client = { ws, userId: message.userId, username: message.username, roomId: 'general' // Could be dynamic };
this.clients.set(clientId, client);
// Add to room
if (!this.rooms.has(client.roomId)) {
this.rooms.set(client.roomId, new Set());
}
this.rooms.get(client.roomId)!.add(clientId);
// Notify room
this.broadcastToRoom(clientId, {
type: 'join',
userId: message.userId,
username: message.username,
timestamp: Date.now()
});
// Send room state to new user
this.sendRoomState(clientId);
}
private broadcastToRoom( senderId: string, message: Message, exclude: string[] = [] ): void { const sender = this.clients.get(senderId); if (!sender) return;
const roomClients = this.rooms.get(sender.roomId);
if (!roomClients) return;
const payload = JSON.stringify(message);
roomClients.forEach(clientId => {
if (!exclude.includes(clientId)) {
const client = this.clients.get(clientId);
if (client && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(payload);
}
}
});
}
private sendRoomState(clientId: string): void { const client = this.clients.get(clientId); if (!client) return;
const roomClients = this.rooms.get(client.roomId);
if (!roomClients) return;
const users = Array.from(roomClients)
.map(id => this.clients.get(id))
.filter(c => c && c.userId !== client.userId)
.map(c => ({ userId: c!.userId, username: c!.username }));
client.ws.send(JSON.stringify({
type: 'room_state',
users,
timestamp: Date.now()
}));
}
private handleDisconnect(clientId: string): void { const client = this.clients.get(clientId); if (!client) return;
// Remove from room
const roomClients = this.rooms.get(client.roomId);
if (roomClients) {
roomClients.delete(clientId);
// Notify others
this.broadcastToRoom(clientId, {
type: 'leave',
userId: client.userId,
username: client.username,
timestamp: Date.now()
});
}
this.clients.delete(clientId);
console.log(`Client disconnected: ${clientId}`);
}
private startHeartbeat(): void { setInterval(() => { this.wss.clients.forEach((ws: any) => { if (ws.isAlive === false) { return ws.terminate(); } ws.isAlive = false; ws.ping(); }); }, 30000); }
private generateId(): string { return Math.random().toString(36).substr(2, 9); } }
// Start server new ChatServer(8080);
- WebSocket Client (React)
// useWebSocket.ts import { useEffect, useRef, useState, useCallback } from 'react';
interface UseWebSocketOptions { url: string; onMessage?: (data: any) => void; onOpen?: () => void; onClose?: () => void; onError?: (error: Event) => void; reconnectAttempts?: number; reconnectInterval?: number; }
export const useWebSocket = (options: UseWebSocketOptions) => { const { url, onMessage, onOpen, onClose, onError, reconnectAttempts = 5, reconnectInterval = 3000 } = options;
const [isConnected, setIsConnected] = useState(false); const [connectionStatus, setConnectionStatus] = useState< 'connecting' | 'connected' | 'disconnected' | 'error'
('connecting');
const wsRef = useRef<WebSocket | null>(null); const reconnectCountRef = useRef(0); const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
const connect = useCallback(() => { try { setConnectionStatus('connecting'); const ws = new WebSocket(url);
ws.onopen = () => {
console.log('WebSocket connected');
setIsConnected(true);
setConnectionStatus('connected');
reconnectCountRef.current = 0;
onOpen?.();
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
onMessage?.(data);
} catch (error) {
console.error('Failed to parse message:', error);
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
setConnectionStatus('error');
onError?.(error);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
setIsConnected(false);
setConnectionStatus('disconnected');
onClose?.();
// Attempt reconnection
if (reconnectCountRef.current < reconnectAttempts) {
reconnectCountRef.current++;
console.log(
`Reconnecting... (${reconnectCountRef.current}/${reconnectAttempts})`
);
reconnectTimeoutRef.current = setTimeout(() => {
connect();
}, reconnectInterval);
}
};
wsRef.current = ws;
} catch (error) {
console.error('Failed to connect:', error);
setConnectionStatus('error');
}
}, [url, onMessage, onOpen, onClose, onError, reconnectAttempts, reconnectInterval]);
const disconnect = useCallback(() => { if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current); } wsRef.current?.close(); wsRef.current = null; }, []);
const send = useCallback((data: any) => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(data)); } else { console.warn('WebSocket is not connected'); } }, []);
useEffect(() => { connect(); return () => { disconnect(); }; }, [connect, disconnect]);
return { isConnected, connectionStatus, send, disconnect, reconnect: connect }; };
// Usage in component const ChatComponent: React.FC = () => { const [messages, setMessages] = useState<any[]>([]);
const { isConnected, send } = useWebSocket({ url: 'ws://localhost:8080', onMessage: (data) => { if (data.type === 'message') { setMessages(prev => [...prev, data]); } }, onOpen: () => { send({ type: 'join', userId: 'user123', username: 'John Doe', timestamp: Date.now() }); } });
const sendMessage = (content: string) => { send({ type: 'message', userId: 'user123', username: 'John Doe', content, timestamp: Date.now() }); };
return ( <div> <div>Status: {isConnected ? 'Connected' : 'Disconnected'}</div> <div> {messages.map((msg, i) => ( <div key={i}>{msg.username}: {msg.content}</div> ))} </div> </div> ); };
- Server-Sent Events (SSE)
// server.ts - SSE endpoint import express from 'express';
const app = express();
interface Client { id: string; res: express.Response; }
class SSEManager { private clients: Client[] = [];
addClient(id: string, res: express.Response): void { // Set SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('Access-Control-Allow-Origin', '*');
this.clients.push({ id, res });
// Send initial connection event
this.sendToClient(id, {
type: 'connected',
clientId: id,
timestamp: Date.now()
});
console.log(`Client ${id} connected. Total: ${this.clients.length}`);
}
removeClient(id: string): void {
this.clients = this.clients.filter(client => client.id !== id);
console.log(Client ${id} disconnected. Total: ${this.clients.length});
}
sendToClient(id: string, data: any): void {
const client = this.clients.find(c => c.id === id);
if (client) {
client.res.write(data: ${JSON.stringify(data)}\n\n);
}
}
broadcast(data: any, excludeId?: string): void {
const message = data: ${JSON.stringify(data)}\n\n;
this.clients.forEach(client => {
if (client.id !== excludeId) {
client.res.write(message);
}
});
}
sendEvent(event: string, data: any): void {
const message = event: ${event}\ndata: ${JSON.stringify(data)}\n\n;
this.clients.forEach(client => {
client.res.write(message);
});
}
}
const sseManager = new SSEManager();
app.get('/events', (req, res) => { const clientId = Math.random().toString(36).substr(2, 9);
sseManager.addClient(clientId, res);
req.on('close', () => { sseManager.removeClient(clientId); }); });
// Simulate real-time updates setInterval(() => { sseManager.broadcast({ type: 'update', value: Math.random() * 100, timestamp: Date.now() }); }, 5000);
app.listen(3000, () => { console.log('SSE server running on port 3000'); });
// client.ts - SSE client class SSEClient { private eventSource: EventSource | null = null; private reconnectAttempts = 0; private maxReconnectAttempts = 5;
connect(url: string, handlers: { onMessage?: (data: any) => void; onError?: (error: Event) => void; onOpen?: () => void; }): void { this.eventSource = new EventSource(url);
this.eventSource.onopen = () => {
console.log('SSE connected');
this.reconnectAttempts = 0;
handlers.onOpen?.();
};
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
handlers.onMessage?.(data);
} catch (error) {
console.error('Failed to parse SSE data:', error);
}
};
this.eventSource.onerror = (error) => {
console.error('SSE error:', error);
handlers.onError?.(error);
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
console.log('Reconnecting to SSE...');
this.connect(url, handlers);
}, 3000);
}
};
// Custom event listeners
this.eventSource.addEventListener('custom-event', (event: any) => {
console.log('Custom event:', JSON.parse(event.data));
});
}
disconnect(): void { this.eventSource?.close(); this.eventSource = null; } }
// Usage const client = new SSEClient(); client.connect('http://localhost:3000/events', { onMessage: (data) => { console.log('Received:', data); }, onOpen: () => { console.log('Connected to server'); } });
- Socket.IO (Production-Ready)
// server.ts import { Server } from 'socket.io'; import { createServer } from 'http';
const httpServer = createServer(); const io = new Server(httpServer, { cors: { origin: process.env.CLIENT_URL || 'http://localhost:3000', methods: ['GET', 'POST'] }, pingTimeout: 60000, pingInterval: 25000 });
// Middleware io.use((socket, next) => { const token = socket.handshake.auth.token; if (isValidToken(token)) { next(); } else { next(new Error('Authentication error')); } });
io.on('connection', (socket) => {
console.log(User connected: ${socket.id});
// Join room socket.on('join-room', (roomId: string) => { socket.join(roomId); socket.to(roomId).emit('user-joined', { userId: socket.id, timestamp: Date.now() }); });
// Handle messages socket.on('message', (data) => { const roomId = Array.from(socket.rooms)[1]; // First is own ID io.to(roomId).emit('message', { ...data, userId: socket.id, timestamp: Date.now() }); });
// Typing indicator socket.on('typing', (isTyping: boolean) => { const roomId = Array.from(socket.rooms)[1]; socket.to(roomId).emit('user-typing', { userId: socket.id, isTyping }); });
socket.on('disconnect', () => {
console.log(User disconnected: ${socket.id});
});
});
httpServer.listen(3001);
function isValidToken(token: string): boolean { // Implement token validation return true; }
Best Practices
✅ DO
-
Implement reconnection logic with exponential backoff
-
Use heartbeat/ping-pong to detect dead connections
-
Validate and sanitize all messages
-
Implement authentication and authorization
-
Handle connection limits and rate limiting
-
Use compression for large payloads
-
Implement proper error handling
-
Monitor connection health
-
Use rooms/channels for targeted messaging
-
Implement graceful shutdown
❌ DON'T
-
Send sensitive data without encryption
-
Keep connections open indefinitely without cleanup
-
Broadcast to all users when targeted messaging suffices
-
Ignore connection state management
-
Send large payloads frequently
-
Skip message validation
-
Forget about mobile/unstable connections
-
Ignore scaling considerations
Performance Optimization
// Message batching class MessageBatcher { private queue: any[] = []; private timer: NodeJS.Timeout | null = null; private batchSize = 10; private batchDelay = 100;
constructor( private sendFn: (messages: any[]) => void ) {}
add(message: any): void { this.queue.push(message);
if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.batchDelay);
}
}
private flush(): void { if (this.queue.length > 0) { this.sendFn(this.queue.splice(0)); } if (this.timer) { clearTimeout(this.timer); this.timer = null; } } }
Resources
-
WebSocket API (MDN)
-
Socket.IO Documentation
-
Server-Sent Events
-
ws - WebSocket Library