How to Implement Real-Time Features with WebSockets and Socket.io 1.x (2016)
Short answer: npm install socket.io, attach to your HTTP server with io = require('socket.io')(server), emit events with socket.emit('event', data), listen on the client with socket.on('event', callback). For authentication: verify JWT in the Socket.io middleware before connection completes. For multiple servers: add socket.io-redis adapter.
Server Setup
// server.js — Node.js 6 + Express + Socket.io 1.x
var express = require('express');
var http = require('http');
var socketio = require('socket.io');
var jwt = require('jsonwebtoken');
var app = express();
var server = http.createServer(app);
var io = socketio(server);
var JWT_SECRET = process.env.JWT_SECRET;
// Authentication middleware for Socket.io
io.use(function(socket, next) {
var token = socket.handshake.query.token ||
socket.handshake.headers['authorization'];
if (!token) {
return next(new Error('Authentication required'));
}
// Remove "Bearer " prefix if present
if (token.startsWith('Bearer ')) {
token = token.slice(7);
}
jwt.verify(token, JWT_SECRET, function(err, decoded) {
if (err) {
return next(new Error('Invalid token'));
}
socket.userId = decoded.userId;
socket.userRole = decoded.role;
next();
});
});
// Connection handler
io.on('connection', function(socket) {
console.log('User ' + socket.userId + ' connected (' + socket.id + ')');
// Auto-join user's private room
socket.join('user:' + socket.userId);
// Admins join admin room
if (socket.userRole === 'admin' || socket.userRole === 'operator') {
socket.join('operators');
}
// Listen for client joining specific order room
socket.on('watch:order', function(orderId) {
// Verify user owns this order or is admin
OrderRepository.isOwnerOrAdmin(orderId, socket.userId, socket.userRole)
.then(function(allowed) {
if (allowed) {
socket.join('order:' + orderId);
socket.emit('watching', { orderId: orderId });
}
});
});
socket.on('disconnect', function() {
console.log('User ' + socket.userId + ' disconnected');
});
});
server.listen(3001, function() {
console.log('WebSocket server on port 3001');
});
module.exports = { io };
Emitting Events from Business Logic
// orderService.js — emit events when orders change
var io = require('./server').io;
function updateOrderStatus(orderId, newStatus, updatedBy) {
return OrderRepository.updateStatus(orderId, newStatus)
.then(function(order) {
// Notify the customer
io.to('user:' + order.userId).emit('order:updated', {
orderId: orderId,
status: newStatus,
updatedAt: new Date().toISOString(),
});
// Notify all operators
io.to('operators').emit('order:status_changed', {
orderId: orderId,
status: newStatus,
customerId: order.userId,
updatedBy: updatedBy,
});
// Notify anyone watching this specific order
io.to('order:' + orderId).emit('order:updated', {
orderId: orderId,
status: newStatus,
updatedAt: new Date().toISOString(),
});
return order;
});
}
Client-Side (Browser)
<!-- index.html -->
<script src="/socket.io/socket.io.js"></script>
<script>
var token = localStorage.getItem('access_token');
var socket = io('https://api.myapp.com', {
query: { token: token },
transports: ['websocket', 'polling'],
// Try WebSocket first, fall back to long-polling
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
});
socket.on('connect', function() {
console.log('Connected to WebSocket server');
// Watch current order
var orderId = document.getElementById('order-id').value;
socket.emit('watch:order', parseInt(orderId));
});
socket.on('order:updated', function(data) {
// Update UI when order status changes
document.getElementById('order-status').textContent = data.status;
showNotification('Order ' + data.orderId + ' is now ' + data.status);
});
socket.on('connect_error', function(error) {
console.error('Connection error:', error.message);
// Fall back to polling the API
startPolling();
});
socket.on('disconnect', function(reason) {
console.log('Disconnected:', reason);
if (reason === 'io server disconnect') {
// Server forced disconnect (e.g., token expired)
socket.connect();
}
});
</script>
Client-Side (React/React Native 2016)
// services/socket.js
import io from 'socket.io-client';
import store from '../store';
var socket = null;
export function connect(token) {
if (socket && socket.connected) return socket;
socket = io('https://api.myapp.com', {
query: { token },
transports: ['websocket'],
});
socket.on('connect', () => {
store.dispatch({ type: 'SOCKET_CONNECTED' });
});
socket.on('order:updated', (data) => {
store.dispatch({ type: 'ORDER_STATUS_UPDATED', payload: data });
});
socket.on('disconnect', () => {
store.dispatch({ type: 'SOCKET_DISCONNECTED' });
});
return socket;
}
export function disconnect() {
if (socket) {
socket.disconnect();
socket = null;
}
}
export function watchOrder(orderId) {
if (socket && socket.connected) {
socket.emit('watch:order', orderId);
}
}
Scaling: Redis Adapter for Multiple Servers
// When running multiple Node.js servers behind a load balancer,
// each server has its own in-memory socket connections.
// socket.io-redis broadcasts events across all servers.
var redis = require('socket.io-redis');
var socketio = require('socket.io');
var io = socketio(server);
io.adapter(redis({ host: 'localhost', port: 6379 }));
// Now: io.to('user:42').emit('event', data)
// Redis pub/sub delivers to ALL servers → ALL sockets for user 42
Connection Limit Management
// Track connected users per server
var connectedUsers = new Map(); // userId → socketId
io.on('connection', function(socket) {
// Enforce one connection per user (optional)
var existing = connectedUsers.get(socket.userId);
if (existing) {
io.to(existing).emit('force_disconnect', { reason: 'new_login' });
}
connectedUsers.set(socket.userId, socket.id);
socket.on('disconnect', function() {
if (connectedUsers.get(socket.userId) === socket.id) {
connectedUsers.delete(socket.userId);
}
});
});
// Monitor connections
setInterval(function() {
var rooms = io.sockets.adapter.rooms;
var operators = rooms['operators'] ? rooms['operators'].length : 0;
console.log('Connections:', io.engine.clientsCount, '| Operators online:', operators);
}, 30000);
Nginx WebSocket Proxy
# WebSocket requires specific nginx headers
location /socket.io/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 600s; # Keep-alive for WebSocket connections
proxy_send_timeout 600s;
}
Dashboard Results (2016)
Operator dashboard before WebSockets: refreshed every 30 seconds via AJAX polling. After:
| AJAX Polling | WebSockets | |
|---|---|---|
| Update latency | 0–30 seconds | < 200ms |
| Server load (50 ops) | 50 × 2 req/min = 100 req/min | 0 requests (push) |
| React time to status change | 30s avg | 0.2s avg |
Operators could now respond to new orders in seconds instead of minutes. The average time-to-first-contact with customers dropped from 4.2 minutes to 38 seconds.