AboutBlogContact
BackendSeptember 15, 2016 5 min read 23

How to Implement Real-Time Features with WebSockets and Socket.io 1.x (2016)

AunimedaAunimeda
📋 Table of Contents

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.

Read Also

How to Add Full-Text Search to Your App with Elasticsearch 2.x (2016)aunimeda
Backend

How to Add Full-Text Search to Your App with Elasticsearch 2.x (2016)

MySQL LIKE queries break at scale. When our product catalog reached 200k items, search took 4+ seconds. Elasticsearch 2.x solved it: 50ms search across 200k documents with relevance scoring, typo tolerance, and faceted filters. Here's the indexing strategy, mapping, and PHP/Node.js integration.

How to Use Redis for Caching in PHP: Cutting Response Times from 800ms to 40ms (2015)aunimeda
Backend

How to Use Redis for Caching in PHP: Cutting Response Times from 800ms to 40ms (2015)

Redis caching reduced our PHP app's average response time from 800ms to 40ms on product listing pages. The pattern: cache database query results with TTL, invalidate on write. Here's the exact implementation with cache key strategy, stampede prevention, and cache warming.

How to Implement JWT Authentication in Node.js + Express (2015)aunimeda
Backend

How to Implement JWT Authentication in Node.js + Express (2015)

JWT became the standard for stateless API authentication in 2015 as mobile apps replaced session cookies. This guide covers the exact implementation: token generation, middleware, refresh tokens, and the security mistakes we made and fixed.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles