100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Web Development

Building a WebSocket Server with Node.js

Learn how to stand up a production-ready WebSocket server in Node.js using the `ws` library, from the handshake through broadcasting and horizontal scaling.

ImplementationIntermediate9 min readJul 10, 2026
Analogies

What Is a WebSocket Server?

A WebSocket server is a long-lived TCP endpoint that, after an initial HTTP handshake, keeps a full-duplex connection open so the server can push data to a client at any time without the client re-requesting it. In Node.js this is typically built with the ws package on top of the built-in http module, since Node's non-blocking event loop is well suited to holding thousands of concurrent sockets open cheaply.

🏏

Cricket analogy: Like a stadium PA system that stays live through the whole innings rather than a scoreboard you must refresh after every over, a WebSocket keeps broadcasting Kohli's runs the instant they happen instead of waiting to be asked.

The Handshake and the `ws` Library

Every WebSocket connection begins as a normal HTTP GET request carrying an Upgrade: websocket header and a Sec-WebSocket-Key; the server responds with HTTP status 101 and a computed Sec-WebSocket-Accept value, after which the TCP socket switches from HTTP framing to the WebSocket binary frame protocol. The ws npm package handles this handshake for you: attaching a WebSocketServer to an existing http.Server lets you share a single port between REST routes and socket upgrades, which is the standard production pattern.

🏏

Cricket analogy: It's like the toss before a match — a short, formal exchange (captain calls heads, umpire confirms) that switches the day from pre-match formality into the actual continuous play of the innings.

javascript
const http = require('http');
const { WebSocketServer } = require('ws');

const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('HTTP endpoint still works alongside the socket server');
});

const wss = new WebSocketServer({ server, path: '/ws' });

wss.on('connection', (socket, request) => {
  console.log('Client connected from', request.socket.remoteAddress);

  socket.on('message', (data) => {
    // Broadcast the message to every other connected client
    wss.clients.forEach((client) => {
      if (client !== socket && client.readyState === client.OPEN) {
        client.send(data.toString());
      }
    });
  });

  socket.on('close', () => console.log('Client disconnected'));
});

server.listen(3000, () => console.log('Listening on :3000'));

Handling Connections, Messages, and Broadcasts

Each accepted connection fires a connection event with a WebSocket instance representing that specific client; from there you attach message, close, and error listeners on the individual socket rather than on the server. Broadcasting to all clients means iterating wss.clients and checking readyState === WebSocket.OPEN before calling send(), since a socket can be mid-close when a broadcast fires and sending to a closed socket throws.

🏏

Cricket analogy: It's like a fielding captain giving individual instructions to each fielder by name over the radio mic rather than shouting one blanket command, and checking each fielder is actually on the field before relaying it.

ws also supports per-message-deflate compression and a perMessageDeflate option; enabling it can cut bandwidth significantly for chatty JSON payloads but adds CPU overhead per message, so benchmark before turning it on for high-throughput servers.

Scaling and Production Considerations

A single Node.js process holding open sockets does not automatically share state across multiple instances, so horizontally scaled deployments need a pub/sub backplane such as Redis to fan messages out across processes, plus sticky sessions or a shared session store so a reconnecting client lands wherever its state lives. Health checks, heartbeats via ping/pong frames, and idle-timeout logic are also essential in production because TCP alone won't reliably detect a half-open connection left behind by a client that lost network without closing cleanly.

🏏

Cricket analogy: It's like the IPL running matches simultaneously in Mumbai and Chennai but still needing a shared central scoring feed so both broadcasts and the official website stay in sync, rather than each stadium keeping its own disconnected scorebook.

Never trust readyState alone to mean the client is actually reachable — a socket can stay OPEN for minutes after the underlying network died. Implement ping/pong heartbeats and terminate sockets that miss several consecutive pongs, or you'll leak memory holding zombie connections.

  • A WebSocket connection starts as an HTTP request with an Upgrade header and switches to persistent, full-duplex framing after a 101 response.
  • Node's ws package attaches a WebSocketServer to an existing http.Server, letting REST and socket traffic share one port.
  • Each client connection is its own WebSocket object; listen for message, close, and error per socket, not globally.
  • Broadcasting means iterating wss.clients and checking readyState === OPEN before calling send().
  • Horizontal scaling requires an external pub/sub layer like Redis so messages fan out across multiple Node processes.
  • Heartbeats (ping/pong) are necessary because TCP alone won't detect a client that disappeared without closing cleanly.
  • perMessageDeflate compression trades CPU for bandwidth and should be benchmarked before enabling in high-throughput servers.

Practice what you learned

Was this page helpful?

Topics covered

#WebDevelopment#WebSocketsStudyNotes#BuildingAWebSocketServerWithNodeJs#Building#WebSocket#Server#Node#StudyNotes#SkillVeris#ExamPrep