WebSockets with Node.js — Real-Time Apps

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

WebSockets enable persistent connections for real-time, bidirectional communication. Perfect for chat, notifications, and live data.

HTTP vs WebSockets

HTTP: Request-response, stateless, overhead WebSockets: Persistent connection, full-duplex, low overhead

Using ws Library

npm install ws
npm install --save-dev @types/ws
import WebSocket from "ws";
import http from "http";

const server = http.createServer();
const wss = new WebSocket.Server({ server });

wss.on("connection", (ws) => {
  console.log("Client connected");

  ws.on("message", (data) => {
    console.log("Received:", data);
    // Broadcast to all clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data);
      }
    });
  });

  ws.on("close", () => {
    console.log("Client disconnected");
  });

  ws.on("error", (error) => {
    console.error("WebSocket error:", error);
  });

  // Send message to client
  ws.send(JSON.stringify({ type: "welcome", message: "Connected" }));
});

server.listen(8080, () => {
  console.log("WebSocket server running on port 8080");
});

Socket.io

Socket.io provides easier API and fallbacks:

npm install socket.io socket.io-client
import { createServer } from "http";
import { Server } from "socket.io";

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: { origin: "*" },
});

io.on("connection", (socket) => {
  console.log(`User ${socket.id} connected`);

  // Listen for events
  socket.on("chat", (message) => {
    console.log("Message:", message);
    // Broadcast to all
    io.emit("chat", {
      userId: socket.id,
      message,
      timestamp: new Date(),
    });
  });

  // Send event to specific user
  socket.emit("welcome", { message: "Welcome to chat" });

  // Join room
  socket.on("join-room", (roomId) => {
    socket.join(roomId);
  });

  // Send to room
  socket.on("room-message", (roomId, message) => {
    io.to(roomId).emit("message", message);
  });

  socket.on("disconnect", () => {
    console.log(`User ${socket.id} disconnected`);
  });
});

httpServer.listen(3000, () => {
  console.log("Socket.io server running on port 3000");
});

Client-Side Usage

import io from "socket.io-client";

const socket = io("http://localhost:3000");

socket.on("connect", () => {
  console.log("Connected to server");
});

socket.on("welcome", (data) => {
  console.log(data);
});

socket.on("chat", (data) => {
  console.log(`${data.userId}: ${data.message}`);
});

// Send event to server
socket.emit("chat", "Hello, World!");

// Join a room
socket.emit("join-room", "room-123");

// Send to room
socket.emit("room-message", "room-123", "Hello, room!");

socket.on("disconnect", () => {
  console.log("Disconnected from server");
});

Chat Application Example

// Server
const io = new Server(httpServer);
const users = new Map<string, string>();

io.on("connection", (socket) => {
  socket.on("login", (username) => {
    users.set(socket.id, username);
    io.emit("users", Array.from(users.values()));
  });

  socket.on("message", (message) => {
    const username = users.get(socket.id);
    io.emit("message", {
      username,
      text: message,
      timestamp: new Date(),
    });
  });

  socket.on("disconnect", () => {
    users.delete(socket.id);
    io.emit("users", Array.from(users.values()));
  });
});

// Client
let username: string;

const usernameInput = document.getElementById("username") as HTMLInputElement;
const sendBtn = document.getElementById("send") as HTMLButtonElement;
const messagesDiv = document.getElementById("messages") as HTMLDivElement;
const usersDiv = document.getElementById("users") as HTMLDivElement;

sendBtn.addEventListener("click", () => {
  const message = (document.getElementById("input") as HTMLInputElement).value;
  socket.emit("message", message);
});

socket.on("message", (data) => {
  const el = document.createElement("div");
  el.textContent = `${data.username}: ${data.text}`;
  messagesDiv.appendChild(el);
});

socket.on("users", (userList) => {
  usersDiv.innerHTML = userList.map((u) => `<p>${u}</p>`).join("");
});

usernameInput.addEventListener("change", (e) => {
  username = (e.target as HTMLInputElement).value;
  socket.emit("login", username);
});

Namespaces and Rooms

// Namespaces - separate communication channels
const chatNs = io.of("/chat");
const notificationNs = io.of("/notifications");

chatNs.on("connection", (socket) => {
  socket.on("message", (msg) => {
    chatNs.emit("message", msg);
  });
});

notificationNs.on("connection", (socket) => {
  socket.on("subscribe", (topic) => {
    socket.join(topic);
  });

  socket.on("notify", (topic, notification) => {
    notificationNs.to(topic).emit("notification", notification);
  });
});

// Client
const chat = io.connect("http://localhost:3000/chat");
const notifications = io.connect("http://localhost:3000/notifications");

Authentication

const io = new Server(httpServer, {
  connectionStateRecovery: {
    maxDisconnectionDuration: 2 * 60 * 1000,
  },
});

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) {
    return next(new Error("Authentication failed"));
  }

  try {
    const decoded = jwt.verify(token, "secret");
    socket.userId = (decoded as any).userId;
    next();
  } catch {
    next(new Error("Invalid token"));
  }
});

io.on("connection", (socket) => {
  console.log(`User ${socket.userId} connected`);
});

// Client
const socket = io("http://localhost:3000", {
  auth: {
    token: localStorage.getItem("token"),
  },
});

Comparison: WebSockets vs Server-Sent Events vs Polling

FeatureWebSocketsSSEPolling
DirectionBidirectionalServer→ClientClient→Server
LatencyLow (~5ms)Low (~100ms)High (seconds)
OverheadLowLowHigh
Browser supportAll modernAll modernAll
ComplexityMediumLowVery low

FAQ

Q: Should I use WebSockets for everything real-time? A: No. SSE is simpler for server→client only. Use WebSockets for two-way communication.

Q: How many concurrent connections can Node.js handle? A: Thousands easily. Architecture matters more than technology.

Q: How do I scale WebSockets across multiple servers? A: Use a message broker (Redis Pub/Sub) to coordinate between servers.


WebSockets bring real-time capabilities to web applications. Combined with Socket.io, they make building collaborative and live-updating applications straightforward.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro