WebSocket & Real-Time

Difficulty: Advanced

Question

How do you implement real-time communication in Node.js? Explain WebSockets and Socket.IO.

Answer

WebSockets provide full-duplex, persistent connections between client and server, enabling real-time bidirectional communication. Unlike HTTP (request-response), WebSocket connections stay open, allowing the server to push data to clients instantly.

Socket.IO is the most popular WebSocket library for Node.js. It builds on top of WebSockets with fallback to long-polling, automatic reconnection, rooms/namespaces for grouping connections, and built-in event handling.

Common real-time use cases include: chat applications, live notifications, collaborative editing, real-time dashboards, gaming, and live location tracking. WebSockets are ideal when you need instant updates without the overhead of repeated HTTP polling.

Socket.IO rooms group connected clients so you can broadcast messages to specific subsets (e.g., all users in a chat room, all admins). Namespaces provide separate communication channels within a single connection (e.g., /chat and /notifications).

Code examples

Socket.IO Server Setup

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

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:5173',
    methods: ['GET', 'POST'],
  },
});

// Connection event
io.on('connection', (socket) => {
  console.log('User connected:', socket.id);

  // Listen for events from client
  socket.on('chat:message', (data) => {
    console.log('Message:', data);
    // Broadcast to all clients except sender
    socket.broadcast.emit('chat:message', {
      user: data.user,
      text: data.text,
      timestamp: new Date(),
    });
  });

  // Typing indicator
  socket.on('chat:typing', (user) => {
    socket.broadcast.emit('chat:typing', user);
  });

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

httpServer.listen(3000, () => {
  console.log('Server with WebSocket on port 3000');
});

Socket.IO wraps the HTTP server. Events are custom-named. broadcast.emit sends to all except the sender. disconnect fires on close.

Rooms and Namespaces

const io = new Server(httpServer);

// Rooms: group clients for targeted broadcasting
io.on('connection', (socket) => {
  // Join a room
  socket.on('room:join', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('room:userJoined', {
      userId: socket.userId,
      roomId,
    });
  });

  // Send message to specific room
  socket.on('room:message', ({ roomId, text }) => {
    io.to(roomId).emit('room:message', {
      user: socket.userId,
      text,
      roomId,
      timestamp: Date.now(),
    });
  });

  // Leave room
  socket.on('room:leave', (roomId) => {
    socket.leave(roomId);
  });
});

// Namespaces: separate communication channels
const chatNs = io.of('/chat');
const notifNs = io.of('/notifications');

chatNs.on('connection', (socket) => {
  console.log('Chat connected:', socket.id);
});

notifNs.on('connection', (socket) => {
  console.log('Notifications connected:', socket.id);
  // Send notification to this user only
  socket.emit('notification', { text: 'Welcome back!' });
});

Rooms let you broadcast to subsets of clients. Namespaces are separate connection scopes on the same underlying connection.

Authentication and Middleware

const jwt = require('jsonwebtoken');

// Socket.IO middleware for auth
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.id;
    socket.userRole = decoded.role;
    next();
  } catch (err) {
    next(new Error('Invalid token'));
  }
});

// Now all connections are authenticated
io.on('connection', (socket) => {
  console.log('Authenticated user:', socket.userId);

  // Join personal room for targeted notifications
  socket.join(`user:${socket.userId}`);
});

// Send notification to specific user from anywhere
function notifyUser(userId, event, data) {
  io.to(`user:${userId}`).emit(event, data);
}

// Client-side connection
// const socket = io('http://localhost:3000', {
//   auth: { token: localStorage.getItem('accessToken') }
// });

Socket.IO middleware runs on connection. Attach user data to the socket object. Use personal rooms (user:ID) for targeted notifications.

Key points

Concepts covered

WebSocket, Socket.IO, Real-Time, Events, Rooms