Intermediate
25 min
Full Guide
WebSockets & Real-Time APIs
Build real-time applications with WebSockets, Server-Sent Events, and Socket.io
What are WebSockets?
WebSockets provide full-duplex, bidirectional communication between client and server over a single, persistent connection. Unlike HTTP where the client always initiates requests, WebSockets allow both client and server to send messages at any time.
This makes WebSockets perfect for real-time applications like chat apps, live notifications, gaming, collaborative editing, and live data feeds.
🔄 HTTP vs WebSocket
HTTP (Request/Response)
- • Client initiates every request
- • Connection closes after response
- • Server can't push data
- • Polling needed for updates
WebSocket (Bidirectional)
- • Both can send anytime
- • Persistent connection
- • Server pushes instantly
- • True real-time updates
Basic WebSocket Connection
// Creating a WebSocket connection
const socket = new WebSocket('wss://api.example.com/ws');
// Connection opened
socket.onopen = function(event) {
console.log('Connected to WebSocket server');
// Send a message
socket.send(JSON.stringify({
type: 'subscribe',
channel: 'notifications'
}));
};
// Receiving messages
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Received:', data);
// Handle different message types
switch (data.type) {
case 'notification':
showNotification(data.payload);
break;
case 'chat_message':
addMessage(data.payload);
break;
case 'user_status':
updateUserStatus(data.payload);
break;
}
};
// Connection closed
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`Connection closed cleanly, code=${event.code}`);
} else {
console.log('Connection lost');
}
};
// Error handling
socket.onerror = function(error) {
console.error('WebSocket error:', error);
};
// Sending messages
function sendMessage(type, payload) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type, payload }));
} else {
console.warn('WebSocket not connected');
}
}
// Close connection
socket.close(1000, 'User logged out');
WebSocket Connection States
🔄
CONNECTING
0
✅
OPEN
1
⏳
CLOSING
2
❌
CLOSED
3
// Checking connection state
if (socket.readyState === WebSocket.OPEN) {
socket.send(message);
}
// State constants
WebSocket.CONNECTING // 0 - Connection in progress
WebSocket.OPEN // 1 - Connection established
WebSocket.CLOSING // 2 - Closing handshake in progress
WebSocket.CLOSED // 3 - Connection closed
Reconnection with Exponential Backoff
// Robust WebSocket with auto-reconnect
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = {
maxRetries: 10,
baseDelay: 1000,
maxDelay: 30000,
...options
};
this.retryCount = 0;
this.handlers = new Map();
this.messageQueue = [];
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket connected');
this.retryCount = 0;
// Send queued messages
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.socket.send(message);
}
this.emit('open');
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.emit('message', data);
// Emit typed events
if (data.type) {
this.emit(data.type, data.payload);
}
};
this.socket.onclose = (event) => {
this.emit('close', event);
if (!event.wasClean) {
this.reconnect();
}
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
};
}
reconnect() {
if (this.retryCount >= this.options.maxRetries) {
console.error('Max reconnection attempts reached');
this.emit('max_retries');
return;
}
// Exponential backoff with jitter
const delay = Math.min(
this.options.baseDelay * Math.pow(2, this.retryCount) +
Math.random() * 1000,
this.options.maxDelay
);
console.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount + 1})`);
setTimeout(() => {
this.retryCount++;
this.connect();
}, delay);
}
send(type, payload) {
const message = JSON.stringify({ type, payload });
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
// Queue message for when connection opens
this.messageQueue.push(message);
}
}
on(event, handler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, []);
}
this.handlers.get(event).push(handler);
}
emit(event, data) {
const handlers = this.handlers.get(event) || [];
handlers.forEach(handler => handler(data));
}
close() {
this.socket.close(1000, 'User initiated close');
}
}
// Usage
const ws = new ReconnectingWebSocket('wss://api.example.com/ws');
ws.on('open', () => console.log('Connected!'));
ws.on('notification', (data) => showNotification(data));
ws.on('chat_message', (data) => addMessage(data));
ws.send('subscribe', { channel: 'updates' });
Chat Application Example
// Real-time chat with WebSockets
class ChatClient {
constructor(serverUrl, userId) {
this.userId = userId;
this.ws = new ReconnectingWebSocket(serverUrl);
this.messageCallbacks = [];
this.ws.on('open', () => {
// Authenticate on connect
this.ws.send('auth', { userId: this.userId });
});
this.ws.on('chat_message', (message) => {
this.messageCallbacks.forEach(cb => cb(message));
});
this.ws.on('user_joined', (user) => {
console.log(`${user.name} joined the chat`);
});
this.ws.on('user_left', (user) => {
console.log(`${user.name} left the chat`);
});
this.ws.on('typing', ({ userId, isTyping }) => {
this.updateTypingIndicator(userId, isTyping);
});
}
joinRoom(roomId) {
this.currentRoom = roomId;
this.ws.send('join_room', { roomId });
}
leaveRoom() {
if (this.currentRoom) {
this.ws.send('leave_room', { roomId: this.currentRoom });
this.currentRoom = null;
}
}
sendMessage(text) {
this.ws.send('chat_message', {
roomId: this.currentRoom,
text,
timestamp: Date.now()
});
}
startTyping() {
this.ws.send('typing', { roomId: this.currentRoom, isTyping: true });
}
stopTyping() {
this.ws.send('typing', { roomId: this.currentRoom, isTyping: false });
}
onMessage(callback) {
this.messageCallbacks.push(callback);
}
updateTypingIndicator(userId, isTyping) {
// Update UI to show "User is typing..."
}
}
// Usage
const chat = new ChatClient('wss://chat.example.com', 'user-123');
chat.joinRoom('room-456');
chat.onMessage((message) => {
addMessageToUI(message);
});
// Send message
sendButton.onclick = () => {
chat.sendMessage(input.value);
input.value = '';
};
// Typing indicator
let typingTimeout;
input.oninput = () => {
chat.startTyping();
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => chat.stopTyping(), 2000);
};
Server-Sent Events (SSE)
For one-way server-to-client streaming, SSE is simpler than WebSockets:
// Server-Sent Events - simpler alternative for one-way updates
const eventSource = new EventSource('https://api.example.com/events');
// Default message event
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
// Named events
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
});
eventSource.addEventListener('price_update', (event) => {
const price = JSON.parse(event.data);
updatePriceDisplay(price);
});
// Connection events
eventSource.onopen = () => {
console.log('SSE connection opened');
};
eventSource.onerror = (error) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('SSE connection closed');
} else {
console.error('SSE error:', error);
}
};
// Close connection
eventSource.close();
// SSE with authentication (using fetch for custom headers)
async function createAuthenticatedSSE(url, token) {
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
handleEvent(data);
}
}
}
}
Socket.io - Popular WebSocket Library
// Socket.io provides additional features:
// - Auto-reconnection
// - Room/namespace support
// - Fallback to HTTP polling
// - Message acknowledgments
import { io } from 'socket.io-client';
// Connect with options
const socket = io('https://api.example.com', {
auth: {
token: 'your-jwt-token'
},
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
// Connection events
socket.on('connect', () => {
console.log('Connected with ID:', socket.id);
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
});
// Sending and receiving messages
socket.emit('chat_message', {
room: 'general',
text: 'Hello everyone!'
});
socket.on('chat_message', (message) => {
addMessageToUI(message);
});
// With acknowledgment (like a callback)
socket.emit('send_message', { text: 'Hello' }, (response) => {
console.log('Message sent, server responded:', response);
});
// Rooms (join/leave)
socket.emit('join_room', 'room-123');
socket.emit('leave_room', 'room-123');
// Namespaces
const adminSocket = io('https://api.example.com/admin');
const chatSocket = io('https://api.example.com/chat');
// Volatile messages (can be dropped if not ready)
socket.volatile.emit('cursor_position', { x: 100, y: 200 });
// Binary data
socket.emit('file_upload', fileBuffer);
Real-Time Data Visualization
// Live stock price updates
class StockTicker {
constructor() {
this.ws = new ReconnectingWebSocket('wss://stocks.example.com');
this.subscriptions = new Set();
this.priceCallbacks = new Map();
this.ws.on('price_update', (data) => {
const callbacks = this.priceCallbacks.get(data.symbol) || [];
callbacks.forEach(cb => cb(data));
});
}
subscribe(symbol, callback) {
if (!this.subscriptions.has(symbol)) {
this.subscriptions.add(symbol);
this.ws.send('subscribe', { symbol });
}
if (!this.priceCallbacks.has(symbol)) {
this.priceCallbacks.set(symbol, []);
}
this.priceCallbacks.get(symbol).push(callback);
}
unsubscribe(symbol) {
this.subscriptions.delete(symbol);
this.priceCallbacks.delete(symbol);
this.ws.send('unsubscribe', { symbol });
}
}
// Usage
const ticker = new StockTicker();
ticker.subscribe('AAPL', (data) => {
document.getElementById('aapl-price').textContent = data.price;
document.getElementById('aapl-change').textContent = data.change;
});
ticker.subscribe('GOOGL', (data) => {
updateChart('GOOGL', data);
});
💡 WebSocket Best Practices
- ✓ Implement reconnection logic - Connections will drop
- ✓ Use exponential backoff - Don't hammer the server
- ✓ Send heartbeats/pings - Keep connection alive
- ✓ Queue messages when disconnected - Send when reconnected
- ✓ Use JSON for messages - Structured, parseable data
- ✓ Handle all connection states - Open, close, error events