On this page
- What are WebSockets?
- Why Use WebSockets?
- WebSocket vs HTTP
- The WebSocket Protocol
- Connection Handshake
- Native WebSocket API
- Client-Side (Browser)
- Server-Side (Node.js with ws library)
- Socket.io: Enhanced WebSockets
- Installation
- Server Implementation
- Client Implementation
- React Integration
- Common Patterns
- Heartbeat/Ping-Pong
- Automatic Reconnection
- Message Queue for Offline Support
- Security Considerations
- 1. Authentication
- 2. Rate Limiting
- 3. Input Validation
- Performance Optimization
- 1. Binary Data
- 2. Compression
- 3. Namespaces for Scaling
- Conclusion
- Next Steps
- Resources
What are WebSockets?
WebSockets provide full-duplex communication channels over a single TCP connection. Unlike HTTP, which follows a request-response pattern, WebSockets enable real-time, bidirectional communication between client and server.
Why Use WebSockets?
- Real-time updates: Instant data push from server to client
- Low latency: No polling overhead
- Efficient: Single persistent connection
- Bidirectional: Both client and server can initiate communication
WebSocket vs HTTP
| Feature | HTTP | WebSocket |
|---|---|---|
| Communication | Request-Response | Full-Duplex |
| Connection | New per request | Persistent |
| Overhead | High (headers) | Low |
| Real-time | Polling required | Native support |
| Use case | REST APIs | Chat, gaming, live updates |
The WebSocket Protocol
Connection Handshake
WebSocket starts as an HTTP request with an upgrade header:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Native WebSocket API
Client-Side (Browser)
// Create WebSocket connection
const socket = new WebSocket('ws://localhost:8080');
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to server');
socket.send('Hello Server!');
});
// Listen for messages
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
// Handle errors
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
// Connection closed
socket.addEventListener('close', (event) => {
console.log('Disconnected from server');
});
Server-Side (Node.js with ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received:', message);
// Echo message back
ws.send(`Echo: ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Socket.io: Enhanced WebSockets
Socket.io provides additional features on top of WebSockets:
- Automatic reconnection
- Room support
- Broadcasting
- Fallback to HTTP long-polling
Installation
npm install socket.io socket.io-client
Server Implementation
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Join a room
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`User ${socket.id} joined room ${roomId}`);
// Notify others in the room
socket.to(roomId).emit('user-joined', socket.id);
});
// Handle messages
socket.on('send-message', (data) => {
// Send to specific room
io.to(data.roomId).emit('receive-message', {
userId: socket.id,
message: data.message,
timestamp: new Date()
});
});
// Broadcasting to all clients
socket.on('broadcast', (data) => {
io.emit('broadcast-message', data);
});
// Disconnect
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
Client Implementation
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
// Connection events
socket.on('connect', () => {
console.log('Connected:', socket.id);
// Join a room
socket.emit('join-room', 'room-123');
});
// Receive messages
socket.on('receive-message', (data) => {
console.log('New message:', data);
displayMessage(data);
});
// Send message
function sendMessage(message) {
socket.emit('send-message', {
roomId: 'room-123',
message: message
});
}
// Handle disconnection
socket.on('disconnect', () => {
console.log('Disconnected from server');
});
React Integration
import { useEffect, useState } from 'react';
import io, { Socket } from 'socket.io-client';
export const useSocket = (url: string) => {
const [socket, setSocket] = useState<Socket | null>(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
const newSocket = io(url);
newSocket.on('connect', () => {
setConnected(true);
});
newSocket.on('disconnect', () => {
setConnected(false);
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}, [url]);
return { socket, connected };
};
// Usage in component
export const ChatComponent = () => {
const { socket, connected } = useSocket('http://localhost:3000');
const [messages, setMessages] = useState([]);
useEffect(() => {
if (!socket) return;
socket.on('receive-message', (message) => {
setMessages(prev => [...prev, message]);
});
return () => {
socket.off('receive-message');
};
}, [socket]);
const sendMessage = (text: string) => {
if (socket && connected) {
socket.emit('send-message', { message: text });
}
};
return (
<div>
<div>Status: {connected ? '🟢 Connected' : '🔴 Disconnected'}</div>
{/* Chat UI */}
</div>
);
};
Common Patterns
Heartbeat/Ping-Pong
Keep connection alive and detect disconnections:
// Server
const HEARTBEAT_INTERVAL = 30000;
io.on('connection', (socket) => {
let isAlive = true;
socket.on('pong', () => {
isAlive = true;
});
const interval = setInterval(() => {
if (!isAlive) {
socket.disconnect();
clearInterval(interval);
return;
}
isAlive = false;
socket.emit('ping');
}, HEARTBEAT_INTERVAL);
socket.on('disconnect', () => {
clearInterval(interval);
});
});
// Client
socket.on('ping', () => {
socket.emit('pong');
});
Automatic Reconnection
const socket = io('http://localhost:3000', {
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconnected after', attemptNumber, 'attempts');
});
socket.on('reconnect_failed', () => {
console.log('Failed to reconnect');
});
Message Queue for Offline Support
class MessageQueue {
constructor(socket) {
this.socket = socket;
this.queue = [];
this.connected = false;
socket.on('connect', () => {
this.connected = true;
this.flushQueue();
});
socket.on('disconnect', () => {
this.connected = false;
});
}
send(event, data) {
if (this.connected) {
this.socket.emit(event, data);
} else {
this.queue.push({ event, data });
}
}
flushQueue() {
while (this.queue.length > 0) {
const { event, data } = this.queue.shift();
this.socket.emit(event, data);
}
}
}
// Usage
const messageQueue = new MessageQueue(socket);
messageQueue.send('send-message', { text: 'Hello' });
Security Considerations
1. Authentication
// Server
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
socket.userId = getUserIdFromToken(token);
next();
} else {
next(new Error('Authentication error'));
}
});
// Client
const socket = io('http://localhost:3000', {
auth: {
token: 'your-jwt-token'
}
});
2. Rate Limiting
const rateLimit = new Map();
io.on('connection', (socket) => {
socket.on('send-message', (data) => {
const userId = socket.userId;
const now = Date.now();
if (!rateLimit.has(userId)) {
rateLimit.set(userId, []);
}
const userRequests = rateLimit.get(userId);
const recentRequests = userRequests.filter(
time => now - time < 60000
);
if (recentRequests.length >= 10) {
socket.emit('error', 'Rate limit exceeded');
return;
}
recentRequests.push(now);
rateLimit.set(userId, recentRequests);
// Process message
});
});
3. Input Validation
const Joi = require('joi');
const messageSchema = Joi.object({
roomId: Joi.string().required(),
message: Joi.string().max(500).required()
});
socket.on('send-message', (data) => {
const { error, value } = messageSchema.validate(data);
if (error) {
socket.emit('error', 'Invalid message format');
return;
}
// Process validated message
});
Performance Optimization
1. Binary Data
// Send binary data (more efficient than JSON)
const buffer = Buffer.from('binary data');
socket.emit('binary-data', buffer);
// Receive binary data
socket.on('binary-data', (buffer) => {
console.log('Received buffer:', buffer);
});
2. Compression
const io = socketIo(server, {
perMessageDeflate: {
threshold: 1024 // Compress messages larger than 1KB
}
});
3. Namespaces for Scaling
// Separate namespaces for different features
const chatNamespace = io.of('/chat');
const notificationNamespace = io.of('/notifications');
chatNamespace.on('connection', (socket) => {
// Handle chat connections
});
notificationNamespace.on('connection', (socket) => {
// Handle notification connections
});
Conclusion
WebSockets enable powerful real-time features in web applications. Key takeaways:
- Use native WebSockets for simple use cases
- Socket.io adds convenience and reliability
- Implement proper error handling and reconnection
- Consider security from the start
- Optimize for performance at scale
Next Steps
- Build the Real-Time Chat Application
- Explore Socket.io rooms and namespaces
- Implement authentication and authorization
- Add presence tracking and typing indicators