#1 India's Top IT Training Institute

×

About Uncodemy

Know more about who we are and what we stand for.

About Us

About Us

Discover our mission to equip individuals with essential IT skills and industry expertise.

Read More →
UnCodeMy Gallery

UnCodeMy Gallery

Explore highlights from our IT training sessions and success stories.

Read More →
×

Free Resources

Learn, practice and plan your career — bilkul free.

Tutorials

Tutorials

Step-by-step written tutorials to learn any technology at your own pace.

Read More →
Free Counselling

Free Counselling

Book a free video counselling session and pick the right course for you.

Read More →
Online Compiler

Online Compiler

Write, run and test your code online — no setup required.

Read More →
Real-Time

A Guide to Build Real-Time Application Using Node.js

Real-time applications are the backbone of modern interactive web experiences — chat apps, live notifications, collaborative tools, gaming, and real-time dashboards. Node.js, with its event-driven, non-blocking architecture, is the platform for building real-time applications. This guide will take you from concept to production-ready real-time application.

Production Reality: Real-time applications require a different mindset than traditional request-response applications. Managing persistent connections, handling disconnects gracefully, and scaling horizontally are critical for production success.

1. What Are Real-Time Applications?

A real-time application delivers data to users instantly as it becomes available, without requiring users to refresh the page. Unlike traditional HTTP requests where the client polls the server, real-time applications maintain a persistent connection and push updates as they happen.

AspectTraditional HTTPReal-Time (WebSockets)
ConnectionShort-lived (request/response)Persistent (bidirectional)
LatencyHigh (polling overhead)Low (instant push)
Server LoadHigh (frequent requests)Low (single connection)
ScalabilityEasier (stateless)Harder (stateful connections)

Common Use Cases

  • 💬 Chat Applications: Slack, WhatsApp, Discord
  • 🔔 Live Notifications: Social media alerts, order updates
  • 👥 Collaborative Tools: Google Docs, Figma, Miro
  • 🎮 Gaming: Multiplayer games, leaderboards
  • 📊 Live Dashboards: Stock prices, sports scores, analytics
  • 🚗 Real-Time Tracking: Uber, food delivery, fleet management

2. WebSocket Protocol — The Foundation

WebSocket is a full-duplex communication protocol that provides a persistent connection between the client and server. Unlike HTTP, which is half-duplex and requires a new request for each response, WebSocket allows bidirectional communication over a single connection.

WebSocket Lifecycle

  • 1. Handshake: Client sends an HTTP upgrade request → Server responds with 101 Switching Protocols.
  • 2. Connection Established: Persistent connection is open.
  • 3. Message Exchange: Both parties can send messages at any time.
  • 4. Close: Either party can close the connection.

WebSocket vs HTTP Long Polling

// ❌ HTTP Long Polling (inefficient)
function poll() {
    fetch('/api/messages')
        .then(res => res.json())
        .then(data => {
            // Process data
            // Wait 1s before next request (adds latency)
            setTimeout(poll, 1000);
        });
}
poll();

// ✅ WebSocket (efficient)
const socket = new WebSocket('ws://localhost:3000/chat');
socket.onmessage = (event) => {
    // Receive messages immediately (no polling)
    console.log(JSON.parse(event.data));
};
socket.send(JSON.stringify({ type: 'message', text: 'Hello!' }));

3. Socket.IO — The Production Choice

While raw WebSockets work, Socket.IO is the industry standard for building real-time applications in production. It provides:

  • Automatic fallback: WebSocket → Long Polling → Other transports (for older browsers).
  • Room support: Broadcast to groups of clients.
  • Reconnection logic: Automatically reconnects when connections drop.
  • Namespaces: Separate concerns (e.g., /chat, /notifications).
  • Authentication: Middleware for auth before connection is established.

Installation

npm install socket.io
npm install socket.io-client  # For client side

Basic Socket.IO Server

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: process.env.CLIENT_URL || 'http://localhost:3000',
        methods: ['GET', 'POST']
    }
});

// ✅ Basic connection handling
io.on('connection', (socket) => {
    console.log('⚡ New client connected:', socket.id);

    socket.on('disconnect', () => {
        console.log('🔴 Client disconnected:', socket.id);
    });

    socket.on('message', (data) => {
        console.log('📩 Message received:', data);
        // Echo back
        socket.emit('message', { echo: data });
    });
});

server.listen(3000, () => {
    console.log('🚀 Server running on port 3000');
});

4. Building a Production-Ready Chat Application

Project Structure

project/
├── src/
│   ├── server.js              # Main server
│   ├── socket/
│   │   ├── index.js           # Socket.IO setup
│   │   ├── handlers.js        # Event handlers
│   │   └── middleware.js      # Auth middleware
│   ├── models/
│   │   ├── Message.js         # Message model
│   │   └── User.js            # User model
│   └── config/
│       └── socket.js          # Socket configuration
└── package.json

Full Chat Application Implementation

// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');

const app = express();
app.use(cors());
app.use(express.json());

// ✅ Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI);

// ✅ Create HTTP server + Socket.IO
const server = http.createServer(app);
const io = new Server(server, {
    cors: { origin: process.env.CLIENT_URL }
});

// ✅ Socket Authentication Middleware
io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    if (!token) {
        return next(new Error('Authentication required'));
    }

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        socket.userId = decoded.userId;
        socket.user = decoded;
        next();
    } catch (err) {
        next(new Error('Invalid token'));
    }
});

// ✅ Socket.IO Event Handlers
io.on('connection', (socket) => {
    const userId = socket.userId;
    console.log(`✅ User ${userId} connected`);

    // 1. Join user to their personal room
    socket.join(`user:${userId}`);

    // 2. Join shared rooms
    socket.on('join-room', (roomId) => {
        socket.join(roomId);
        console.log(`User ${userId} joined room: ${roomId}`);
        socket.to(roomId).emit('user-joined', { userId, roomId });
    });

    // 3. Leave room
    socket.on('leave-room', (roomId) => {
        socket.leave(roomId);
        console.log(`User ${userId} left room: ${roomId}`);
        socket.to(roomId).emit('user-left', { userId, roomId });
    });

    // 4. Send message
    socket.on('send-message', async (data) => {
        try {
            const { roomId, text, type = 'text' } = data;

            // Save message to database
            const message = new Message({
                roomId,
                userId,
                text,
                type,
                createdAt: new Date()
            });
            await message.save();

            // Broadcast to everyone in the room
            const messageData = {
                id: message._id,
                userId,
                text,
                type,
                createdAt: message.createdAt,
                username: socket.user?.username || 'Unknown'
            };

            io.to(roomId).emit('new-message', messageData);

            // Send acknowledgment to sender
            socket.emit('message-sent', { id: message._id, status: 'delivered' });

        } catch (err) {
            console.error('Message error:', err);
            socket.emit('message-error', { error: err.message });
        }
    });

    // 5. Typing indicator
    socket.on('typing-start', ({ roomId, userId }) => {
        socket.to(roomId).emit('user-typing', { userId, isTyping: true });
    });

    socket.on('typing-end', ({ roomId, userId }) => {
        socket.to(roomId).emit('user-typing', { userId, isTyping: false });
    });

    // 6. Fetch message history
    socket.on('get-history', async ({ roomId, limit = 50, before }) => {
        try {
            const query = { roomId };
            if (before) {
                query.createdAt = { $lt: new Date(before) };
            }
            const messages = await Message.find(query)
                .sort({ createdAt: -1 })
                .limit(limit)
                .populate('userId', 'username avatar')
                .lean();

            socket.emit('message-history', messages.reverse());
        } catch (err) {
            socket.emit('history-error', { error: err.message });
        }
    });

    // 7. Mark messages as read
    socket.on('mark-read', async ({ messageIds }) => {
        try {
            await Message.updateMany(
                { _id: { $in: messageIds }, userId: { $ne: socket.userId } },
                { read: true }
            );
            socket.emit('messages-read', { messageIds });
        } catch (err) {
            console.error('Mark read error:', err);
        }
    });

    // 8. Disconnect
    socket.on('disconnect', () => {
        console.log(`🔴 User ${userId} disconnected`);
        // Broadcast user offline status
        io.emit('user-offline', { userId });
    });

    // 9. Error handling
    socket.on('error', (err) => {
        console.error(`Socket error for user ${userId}:`, err);
        socket.emit('socket-error', { error: err.message });
    });
});

// ✅ API endpoint to get active rooms
app.get('/api/rooms', async (req, res) => {
    // Fetch rooms from database
    const rooms = await Room.find().populate('users', 'username avatar');
    res.json(rooms);
});

// ✅ Start server
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
    console.log(`🚀 Server running on port ${PORT}`);
});

// ✅ Graceful shutdown
process.on('SIGTERM', () => {
    console.log('📦 Shutting down gracefully...');
    io.close(() => {
        server.close(() => {
            console.log('✅ Server closed');
            process.exit(0);
        });
    });
});

Socket.IO Middleware for JWT Authentication

// socket/middleware.js
const jwt = require('jsonwebtoken');

const authenticateSocket = (io) => {
    io.use((socket, next) => {
        const token = socket.handshake.auth.token;

        if (!token) {
            return next(new Error('Authentication required'));
        }

        try {
            const decoded = jwt.verify(token, process.env.JWT_SECRET);
            socket.userId = decoded.userId;
            socket.user = decoded;
            next();
        } catch (err) {
            if (err.name === 'TokenExpiredError') {
                return next(new Error('Token expired'));
            }
            return next(new Error('Invalid token'));
        }
    });
};

module.exports = { authenticateSocket };

Client-Side Implementation

// ✅ React Client Example
import { useEffect, useState, useRef } from 'react';
import { io } from 'socket.io-client';

function ChatApp() {
    const [messages, setMessages] = useState([]);
    const [isConnected, setIsConnected] = useState(false);
    const socketRef = useRef(null);
    const token = localStorage.getItem('accessToken');

    useEffect(() => {
        // ✅ Connect to socket
        socketRef.current = io('http://localhost:5000', {
            auth: { token },
            transports: ['websocket'],
            reconnection: true,
            reconnectionAttempts: 5,
            reconnectionDelay: 1000
        });

        const socket = socketRef.current;

        socket.on('connect', () => {
            console.log('✅ Connected to server');
            setIsConnected(true);
        });

        socket.on('disconnect', () => {
            console.log('🔴 Disconnected from server');
            setIsConnected(false);
        });

        socket.on('connect_error', (err) => {
            console.error('❌ Connection error:', err.message);
            if (err.message === 'Authentication required') {
                // Redirect to login
                window.location.href = '/login';
            }
        });

        // ✅ Receive new messages
        socket.on('new-message', (message) => {
            setMessages(prev => [...prev, message]);
        });

        socket.on('message-sent', (data) => {
            console.log('✅ Message sent:', data);
        });

        socket.on('message-history', (history) => {
            setMessages(history);
        });

        socket.on('user-typing', ({ userId, isTyping }) => {
            // Show/hide typing indicator
            setTypingUsers(prev => {
                if (isTyping) {
                    return [...prev, userId];
                }
                return prev.filter(id => id !== userId);
            });
        });

        // ✅ Cleanup on unmount
        return () => {
            socket.disconnect();
        };
    }, [token]);

    // ✅ Send message
    const sendMessage = (text) => {
        if (!socketRef.current || !text.trim()) return;
        socketRef.current.emit('send-message', {
            roomId: 'general',
            text: text.trim()
        });
        setInput('');
    };

    // ✅ Typing indicator
    const handleTyping = (isTyping) => {
        socketRef.current.emit(
            isTyping ? 'typing-start' : 'typing-end',
            { roomId: 'general' }
        );
    };

    return (
        <div>
            <div className="chat-window">
                {messages.map(msg => (
                    <div key={msg.id} className="message">
                        <strong>{msg.username}:</strong> {msg.text}
                    </div>
                ))}
            </div>
            <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyUp={(e) => {
                    if (e.key === 'Enter') sendMessage(input);
                }}
                onFocus={() => handleTyping(true)}
                onBlur={() => handleTyping(false)}
            />
            <button onClick={() => sendMessage(input)}>Send</button>
        </div>
    );
}

5. Scaling Real-Time Applications for Production

Production Reality: Scaling real-time applications is fundamentally different from scaling HTTP services. You need to manage persistent connections across multiple server instances.

5.1 Use Redis Adapter for Multi-Server Scaling

// ✅ Socket.IO with Redis Adapter
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();

Promise.all([pubClient.connect(), subClient.connect()])
    .then(() => {
        io.adapter(createAdapter(pubClient, subClient));
        console.log('✅ Redis adapter connected');
    })
    .catch(err => {
        console.error('❌ Redis connection failed:', err);
    });

5.2 Sticky Sessions (or Enable Socket.IO Sticky Session)

With sticky sessions, requests from the same client are always routed to the same server instance. This is critical for WebSocket connections.

// Using nginx for sticky sessions
// nginx.conf
upstream socket_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

server {
    location /socket.io/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://socket_backend;
        proxy_buffering off;
        proxy_redirect off;
        proxy_read_timeout 60s;
        # Sticky session
        proxy_set_header Cookie $http_cookie;
    }
}

5.3 Horizontal Scaling — PM2 Cluster Mode

// ✅ PM2 cluster mode
// ecosystem.config.js
module.exports = {
    apps: [{
        name: 'socket-app',
        script: 'src/server.js',
        instances: 'max', // Use all CPU cores
        exec_mode: 'cluster',
        env: {
            NODE_ENV: 'production',
            PORT: 3000
        },
        watch: false,
        max_memory_restart: '1G'
    }]
};

// Run: pm2 start ecosystem.config.js

5.4 Handling Disconnections & Reconnections

// ✅ Client-side reconnection strategy
const socket = io('https://api.example.com', {
    reconnection: true,
    reconnectionAttempts: 10,
    reconnectionDelay: 1000,
    reconnectionDelayMax: 5000,
    timeout: 20000,
    transports: ['websocket']
});

socket.on('connect_error', (error) => {
    if (error.message === 'Authentication required') {
        // Refresh token and reconnect
        refreshToken().then(() => {
            socket.auth.token = localStorage.getItem('accessToken');
            socket.connect();
        });
    }
});

// ✅ Server-side cleanup on disconnect
io.on('connection', (socket) => {
    // Register cleanup
    socket.on('disconnect', async (reason) => {
        // Remove user from rooms
        const rooms = Array.from(socket.rooms);
        for (const room of rooms) {
            if (room !== socket.id) {
                socket.leave(room);
                socket.to(room).emit('user-left', { userId: socket.userId });
            }
        }

        // Update user status in database
        await User.findByIdAndUpdate(socket.userId, {
            lastSeen: new Date(),
            isOnline: false
        });

        console.log(`User ${socket.userId} disconnected: ${reason}`);
    });
});

6. Performance Optimization

6.1 Message Batching & Compression

// ✅ Batch messages before sending
const messageQueue = new Map();

socket.on('send-message', (data) => {
    const { roomId, text } = data;
    const message = { userId: socket.userId, text, timestamp: Date.now() };

    if (!messageQueue.has(roomId)) {
        messageQueue.set(roomId, []);
        // Flush queue every 100ms
        setTimeout(() => flushQueue(roomId), 100);
    }
    messageQueue.get(roomId).push(message);
});

function flushQueue(roomId) {
    const messages = messageQueue.get(roomId) || [];
    if (messages.length > 0) {
        io.to(roomId).emit('batch-messages', messages);
        messageQueue.delete(roomId);
    }
}

6.2 Throttle & Debounce High-Frequency Events

// ✅ Debounce typing indicators
const typingTimers = new Map();

socket.on('typing-start', ({ roomId }) => {
    // Clear existing timer
    clearTimeout(typingTimers.get(roomId));
    typingTimers.set(roomId, setTimeout(() => {
        socket.to(roomId).emit('user-typing', { userId: socket.userId, isTyping: false });
    }, 1000));
});

7. Monitoring & Observability

7.1 Socket.IO Metrics

// ✅ Monitor active connections
setInterval(() => {
    const connections = io.sockets.sockets.size;
    const rooms = io.sockets.adapter.rooms.size;
    console.log(`📊 Connections: ${connections}, Rooms: ${rooms}`);
    // Send to monitoring system (Datadog, Prometheus, etc.)
}, 30000);

7.2 Health Check Endpoint

// ✅ Health check for load balancers
app.get('/health', (req, res) => {
    const connectionCount = io.sockets.sockets.size;
    const status = connectionCount > 0 ? 'healthy' : 'degraded';
    res.json({
        status,
        timestamp: new Date().toISOString(),
        connections: connectionCount,
        uptime: process.uptime()
    });
});

8. Production-Ready Checklist

  • ✅ Use Socket.IO — Not raw WebSockets for production.
  • ✅ Implement authentication — JWT middleware on socket connection.
  • ✅ Use Redis adapter — For horizontal scaling across multiple servers.
  • ✅ Handle disconnections — Clean up resources and update user status.
  • ✅ Implement reconnection — With exponential backoff.
  • ✅ Rate limit events — Prevent abuse.
  • ✅ Log events — For debugging and monitoring.
  • ✅ Add health checks — For load balancers and Kubernetes.
  • ✅ Monitor connections — Track active connections and rooms.
  • ✅ Use sticky sessions — For multi-server deployment.
  • ✅ Implement message persistence — Store messages in database.
  • ✅ Add error handling — Graceful degradation.
  • ✅ Use environment variables — For configuration.
  • ✅ Set up graceful shutdown — Close connections properly.

9. Quick Reference — Socket.IO Events

MethodDescriptionExample
io.on('connection')Handle new connectionsio.on('connection', (socket) => {})
socket.emit(event, data)Send to a specific clientsocket.emit('message', data)
io.emit(event, data)Broadcast to all clientsio.emit('notification', data)
socket.to(room).emit()Send to clients in a roomsocket.to('general').emit('message', data)
socket.broadcast.emit()Send to all except sendersocket.broadcast.emit('user-joined', data)
socket.join(room)Join a roomsocket.join('room-123')
socket.leave(room)Leave a roomsocket.leave('room-123')
socket.disconnect()Disconnect clientsocket.disconnect()
socket.on('disconnect')Handle disconnectionsocket.on('disconnect', () => {})
Production Wisdom: Real-time applications are the most engaging applications on the web. They create user experiences that were impossible just a few years ago. With Node.js and Socket.IO, you can build production-ready real-time applications that scale to millions of concurrent users. The key is to start with a solid architecture, plan for growth, and monitor everything.

Ready to master Node.js?

Build scalable, high-performance backend applications with Node.js. Learn from industry experts with hands-on projects.

Explore Course