Introduction
WebSockets enable bi-directional, real-time communication between clients and servers. Unlike HTTP requests, connections stay open, allowing instant data exchange. This guide covers building real-time applications with WebSockets.
What Are WebSockets
How WebSockets Work
- Client initiates HTTP upgrade request
- Server accepts upgrade
- WebSocket connection established
- Both sides can send messages anytime
WebSocket vs HTTP
| Aspect | HTTP | WebSocket |
|---|---|---|
| Direction | Request-response | Bi-directional |
| Connection | New each request | Persistent |
| Overhead | Headers each time | Minimal after connect |
| Use case | REST APIs | Real-time |
Native WebSocket API
Client-Side
const ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => {
console.log('Connected to server');
ws.send('Hello, server!');
};
ws.onmessage = (event) => {
console.log('Message from server:', event.data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Connection closed');
};
// Send message
ws.send(JSON.stringify({ type: 'hello', data: 'world' }));
// Close connection
ws.close();
Server-Side (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received:', message.toString());
// Echo back
ws.send(`Server received: ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Socket.io
Why Socket.io
- Fallback support (polling)
- Automatic reconnection
- Room-based messaging
- Event-based API
Server Setup
const { Server } = require('socket.io');
const io = new Server(3000, {
cors: {
origin: '*'
}
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Join room
socket.on('join-room', (room) => {
socket.join(room);
console.log(`Socket ${socket.id} joined room ${room}`);
});
// Handle events
socket.on('chat-message', (data) => {
// Broadcast to room
io.to(data.room).emit('new-message', data);
});
// Private message
socket.on('private-message', ({ to, message }) => {
io.to(to).emit('private-message', {
from: socket.id,
message
});
});
socket.on('disconnect', () => {
console.log('User disconnected');
});
});
Client Setup
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
// Join room
socket.emit('join-room', 'room-123');
// Send message
socket.emit('chat-message', {
room: 'room-123',
message: 'Hello everyone!'
});
// Listen for messages
socket.on('new-message', (data) => {
console.log('New message:', data.message);
});
</script>
Room Management
Basic Rooms
// Join room
socket.join('room-1');
// Leave room
socket.leave('room-1');
// Send to specific room
io.to('room-1').emit('event', data);
// Send to all rooms except sender
socket.to('room-1').emit('event', data);
Multiple Rooms
// User in multiple rooms
socket.join('user-123');
socket.join('notifications');
socket.join('chat');
// Broadcast to all user's rooms
socket.to(['user-123', 'notifications']).emit('update', {});
Broadcasting Patterns
Broadcast to All
io.emit('broadcast', { message: 'Hello everyone!' });
Broadcast to Room
io.to('room-1').emit('room-message', { text: 'Hello room!' });
Broadcast to Others
// Everyone except sender
socket.broadcast.emit('others', { message: 'Not from me' });
// Everyone in room except sender
socket.to('room-1').emit('others', { message: 'Not from me' });
Scaling WebSockets
Horizontal Scaling
// Using Redis adapter for scaling
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
Sticky Sessions
# Nginx configuration
upstream backend {
server backend1:3000;
server backend2:3001;
}
server {
location /socket.io {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Authentication
Connection Authentication
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = verifyToken(token);
socket.user = user;
next();
} catch (err) {
next(new Error('Unauthorized'));
}
});
io.on('connection', (socket) => {
console.log('Authenticated user:', socket.user.id);
});
Middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
next();
} else {
next(new Error('Authentication error'));
}
});
Error Handling
Connection Errors
io.on('connection', (socket) => {
socket.on('error', (err) => {
console.error('Socket error:', err);
});
});
Reconnection
// Client-side
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.on('reconnect_attempt', (attempt) => {
console.log('Reconnection attempt:', attempt);
});
socket.on('reconnect', () => {
console.log('Reconnected!');
});
Practical Examples
Chat Application
// Server
io.on('connection', (socket) => {
socket.on('join-chat', ({ roomId, userId }) => {
socket.join(roomId);
io.to(roomId).emit('user-joined', { userId });
});
socket.on('send-message', ({ roomId, message, userId }) => {
const messageData = {
id: Date.now(),
userId,
message,
timestamp: new Date()
};
io.to(roomId).emit('new-message', messageData);
});
socket.on('typing', ({ roomId, userId }) => {
socket.to(roomId).emit('user-typing', { userId });
});
});
Live Notifications
// Notification service
function sendNotification(userId, notification) {
io.to(`user-${userId}`).emit('notification', notification);
}
// User joins their notification room
socket.on('join-notifications', (userId) => {
socket.join(`user-${userId}`);
});
Best Practices
Security
- Always authenticate connections
- Validate message data
- Rate limit messages
- Use TLS (wss://)
Performance
- Don’t store too much in memory
- Use rooms for scalability
- Implement heartbeat/ping-pong
- Clean up disconnected sockets
Architecture
- Separate WebSocket service
- Use message queues for reliability
- Implement persistence for messages
Conclusion
WebSockets enable powerful real-time features. Start simple with native WebSockets, then use Socket.io for production. Remember to plan for scaling and always implement proper authentication.
Comments