DEV Community

Cover image for WebSocket Server: How to Build One in Node.js — and When to Use a Managed One
alakkadshaw
alakkadshaw

Posted on • Originally published at Medium

WebSocket Server: How to Build One in Node.js — and When to Use a Managed One

A WebSocket server is a long-lived TCP process that keeps an open, two-way connection to each client so either side can push messages the instant they happen — no polling, no re-connecting per request.

In Node.js you can stand one up in about fifteen lines with the ws library, and this guide builds a runnable one (broadcast, then rooms and presence) below. The harder question isn't how to build one — it's whether you should run one yourself, and this guide answers that too.

That fork is the whole article. If you want to own the infrastructure, the build path is a small Node + ws server you can copy-paste and run right now.

If you'd rather not operate, scale, secure, and keep a fleet of stateful socket servers alive forever, the buy path or there are free options also available like Metered Realtime is a managed WebSocket service you connect to with one import. We build the small one first, so the managed one isn't a black box.

TL;DR: A minimal WebSocket server in Node.js is ~15 lines with ws — we build broadcast, then rooms + presence, both runnable (and the same server in Python). The code is the easy 5%. The hard 95% is everything a toy server ignores: reconnection, auth, backpressure, horizontal scale across many boxes, and the reliability engineering behind five-nines uptime. Self-host when you need deep infrastructure control or on-prem/compliance; otherwise a managed WebSocket service is almost always the better trade once you count engineering time, maintenance, and server cost.

Companion tutorial: if your goal is specifically WebRTC, the WebRTC Signaling Server guide builds a signaling relay on top of exactly this pattern — a WebSocket server is the transport most WebRTC signaling runs on.

A WebSocket server holds a persistent two-way connection to each browser client, pushing messages in both directions without polling.

What a WebSocket server actually is

A normal HTTP request is one round trip: the client asks, the server answers, the connection closes. That's fine for loading a page, but it's a bad fit for anything live — chat, presence, dashboards, multiplayer, notifications — because the server can't speak until it's spoken to.

A WebSocket connection is different. The client and server do a one-time HTTP "upgrade" handshake, and after that the socket stays open.

Either side can send a message at any moment, in either direction, with almost no per-message overhead. That persistent, full-duplex channel is the whole value.

It helps to see WebSockets next to the alternatives:

Approach Direction Connection Best for
Polling / long-polling client pulls repeated HTTP requests simple, low-frequency updates
Server-Sent Events (SSE) server → client only one long-lived HTTP stream one-way feeds (notifications, logs)
WebSocket both directions one persistent socket chat, presence, multiplayer, live collaboration

A WebSocket server, then, is the process that accepts those upgraded connections, holds one open socket per client, and decides what to do with each incoming message — usually routing it to other clients. It's stateful (it remembers who's connected) and long-lived (it doesn't return a response and forget you), which is exactly why it's more work to operate than a stateless HTTP endpoint.

Article Contents

  1. Build a WebSocket Server in Node.js (with ws)
  2. The Free Managed Option: Metered realtime Messaging

Build a WebSocket server in Node.js (with ws)

The ws library is the de-facto WebSocket implementation for Node — small, fast, zero-fuss. Here's the smallest useful server: it accepts connections and relays every message it receives to all the other connected clients (a broadcast bus).

Create a folder, then:

npm init -y
npm pkg set type=module
npm install ws
Enter fullscreen mode Exit fullscreen mode

Save this as server.js:

// server.js — the smallest useful WebSocket server: relay every message to all other clients.
import { WebSocketServer, WebSocket } from "ws";

const PORT = process.env.PORT || 8080;
const wss = new WebSocketServer({ port: PORT });

wss.on("connection", (socket) => {
  console.log("client connected — total:", wss.clients.size);

  socket.on("message", (data, isBinary) => {
    // fan the message out to everyone except the sender
    for (const client of wss.clients) {
      if (client !== socket && client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: isBinary });
      }
    }
  });

  socket.on("close", () => {
    console.log("client disconnected — total:", wss.clients.size);
  });
});

console.log(`WebSocket server listening on ws://localhost:${PORT}`);
Enter fullscreen mode Exit fullscreen mode

Run it:

node server.js
# WebSocket server listening on ws://localhost:8080
Enter fullscreen mode Exit fullscreen mode

That's a working WebSocket server. Three things are worth noticing: wss.clients is a live Set of every open socket, readyState === WebSocket.OPEN guards against sockets mid-close (calling .send() on a closing socket throws), and we skip the sender so people don't see their own messages echoed back.

Add rooms, presence, and a heartbeat

A single broadcast bus is rarely what you want — real apps have rooms (chat channels, game lobbies, documents) and need to know who's present. That means a message protocol. WebSocket frames are just bytes, so a tiny JSON envelope with a type field is the standard move.

Production servers also need a heartbeat to notice sockets that died without a clean close (a common gotcha — a yanked network cable leaves a "connection" that's really gone).

Save this as rooms-server.js:

// rooms-server.js — a WebSocket server with rooms (channels), presence, a JSON protocol, and heartbeat.
import { WebSocketServer, WebSocket } from "ws";

const PORT = process.env.PORT || 8080;
const wss = new WebSocketServer({ port: PORT });

// which room each socket is in
const roomOf = new Map();

function broadcast(room, message, except) {
  const payload = JSON.stringify(message);
  for (const client of wss.clients) {
    if (client !== except && client.readyState === WebSocket.OPEN && roomOf.get(client) === room) {
      client.send(payload);
    }
  }
}

wss.on("connection", (socket) => {
  socket.isAlive = true;
  socket.on("pong", () => { socket.isAlive = true; });

  socket.on("message", (raw) => {
    let msg;
    try {
      msg = JSON.parse(raw);
    } catch {
      return socket.send(JSON.stringify({ type: "error", error: "invalid_json" }));
    }

    if (msg.type === "join") {
      roomOf.set(socket, msg.room);
      socket.send(JSON.stringify({ type: "joined", room: msg.room }));
      broadcast(msg.room, { type: "presence", event: "peer-joined" }, socket);
    } else if (msg.type === "message") {
      const room = roomOf.get(socket);
      if (room) broadcast(room, { type: "message", data: msg.data }, socket);
    }
  });

  socket.on("close", () => {
    const room = roomOf.get(socket);
    if (room) broadcast(room, { type: "presence", event: "peer-left" }, socket);
    roomOf.delete(socket);
  });
});

// heartbeat: every 30s, drop any socket that didn't answer the previous ping
const heartbeat = setInterval(() => {
  for (const socket of wss.clients) {
    if (socket.isAlive === false) {
      socket.terminate();
      continue;
    }
    socket.isAlive = false;
    socket.ping();
  }
}, 30000);
wss.on("close", () => clearInterval(heartbeat));

console.log(`WebSocket rooms server listening on ws://localhost:${PORT}`);
Enter fullscreen mode Exit fullscreen mode

A client sends {"type":"join","room":"lobby"} to enter a room, {"type":"message","data":"…"} to talk to it, and everyone in the room gets peer-joined / peer-left presence events automatically. The heartbeat block at the bottom pings every socket every 30 seconds and terminates any that didn't pong back since the last round — that's how you reclaim dead connections. Notice it's already fiddly, and it's just one of the hardening items we'll list shortly.

A browser client

Save this as index.html and open it in two tabs (run node rooms-server.js first):

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>WebSocket chat demo</title>
    <style>
      body { font: 16px/1.5 system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; padding: 0 1rem; }
      #log { border: 1px solid #cbd5e1; border-radius: 8px; height: 16rem; overflow-y: auto; padding: .75rem; margin-bottom: .75rem; }
      #log div { padding: .1rem 0; }
      form { display: flex; gap: .5rem; }
      input { flex: 1; padding: .5rem; border: 1px solid #cbd5e1; border-radius: 8px; }
      button { padding: .5rem 1rem; border: 0; border-radius: 8px; background: #6d5efc; color: #fff; cursor: pointer; }
    </style>
  </head>
  <body>
    <h1>WebSocket chat</h1>
    <div id="log"></div>
    <form id="form">
      <input id="input" autocomplete="off" placeholder="Type a message…" />
      <button type="submit">Send</button>
    </form>

    <script>
      const log = (line) => {
        const el = document.getElementById("log");
        el.append(Object.assign(document.createElement("div"), { textContent: line }));
        el.scrollTop = el.scrollHeight;
      };

      const ws = new WebSocket("ws://localhost:8080");

      ws.addEventListener("open", () => {
        log("· connected");
        ws.send(JSON.stringify({ type: "join", room: "lobby" }));
      });

      ws.addEventListener("message", (event) => {
        const msg = JSON.parse(event.data);
        if (msg.type === "joined") log("· joined room: " + msg.room);
        else if (msg.type === "presence") log("· " + msg.event);
        else if (msg.type === "message") log("peer: " + msg.data);
      });

      ws.addEventListener("close", () => log("· disconnected"));

      document.getElementById("form").addEventListener("submit", (e) => {
        e.preventDefault();
        const input = document.getElementById("input");
        const text = input.value.trim();
        if (!text) return;
        ws.send(JSON.stringify({ type: "message", data: text }));
        log("you: " + text);
        input.value = "";
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Type in one tab and it appears in the other. Open a third tab to watch peer-joined fire. You now have a real, working WebSocket server with rooms and presence — the complete, runnable code is linked at the end.

The same server in Python

The protocol is language-agnostic, so the build looks the same in any stack. Here's the broadcast server again as a python websocket server, using the websockets library. Save it as server.py:

# server.py — a minimal WebSocket broadcast server in Python (websockets library).
import asyncio
from websockets.asyncio.server import serve

clients = set()

async def handler(socket):
    clients.add(socket)
    try:
        async for message in socket:
            # fan the message out to everyone except the sender
            for client in clients:
                if client is not socket:
                    await client.send(message)
    finally:
        clients.discard(socket)

async def main():
    async with serve(handler, "localhost", 8080):
        print("WebSocket server listening on ws://localhost:8080")
        await asyncio.get_running_loop().create_future()  # run forever

asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode
pip install websockets
python server.py
Enter fullscreen mode Exit fullscreen mode

Same shape — accept a socket, hold it, relay to everyone else. Go (gorilla/websocket) and Java (Spring's @ServerEndpoint) follow the identical pattern. What changes below is not the language; it's the operational load.

What this toy server ignores (the hard 95%)

The code above is complete and correct — and it is nowhere near production. The forty lines are the easy part. Here's the part that isn't, and it's the same list in every language:

  • Reconnection. Networks drop. Laptops sleep, phones roam Wi-Fi→cellular, load balancers cut idle sockets. A real client needs exponential backoff with jitter; a real server needs to not fall over when thousands reconnect at once (the "thundering herd").
  • Authentication & authorization. Who is this socket? Which rooms may it join? Our server trusts anyone who connects. Production needs token auth at the handshake, per-room permission checks on every message, and an Origin-header check at the upgrade — WebSockets aren't covered by the browser's same-origin policy, so without it any site can open a cross-site socket to your server (CSWSH).
  • Backpressure. A slow client whose send buffer fills up will balloon your server's memory. You have to watch socket.bufferedAmount and shed or disconnect. A toy server just OOMs.
  • Horizontal scale. One process holds sockets in one Map in one box's memory. The moment you need a second box, "who's in lobby?" spans machines — now you need a shared pub/sub backplane (Redis, NATS) so servers can reach clients they don't personally hold.
  • TLS, presence at scale, message limits, idle timeouts, metrics, graceful deploys — every one is a project.
  • Reliability. This is the quiet giant. Committing to — and actually delivering — five-nines (99.999%) uptime is roughly five minutes of downtime per year, across deploys, cloud incidents, and traffic spikes. That is genuinely hard engineering, and it never ships as a weekend project.

The visible WebSocket relay code is a small tip; below the waterline sit reconnection, auth, backpressure, horizontal scale, and reliability

Build vs buy: the honest decision

So should you run your own WebSocket server or use a managed one? Here's the straight version, no hedging.

Run it yourself when you genuinely need it: deep infrastructure control (custom protocols, exotic routing, data that legally cannot leave your network), or a compliance/corporate mandate for on-premise deployment. Those are real reasons, and self-hosting is the right call there.

In every other case, a managed WebSocket service is usually the better trade — because the forty lines were never the cost. Getting from the toy above to something you'd trust in production — auth, reconnection, backpressure, a pub/sub backplane for multiple boxes, metrics, and deploys that don't drop every connection — is realistically a few engineer-weeks up front, then a permanent line item of maintenance and on-call, plus the server bill for boxes sized to your peak concurrency. Weighed against a service with a free tier, the math rarely favors DIY.

Factor Self-hosted ws Managed WebSocket service
Time to first message Minutes (the code above) Minutes (an import + a key)
Reconnection logic You build & tune it Built in
Auth / permissions You build it Built in (keys / tokens)
Horizontal scale You add a pub/sub backplane Handled for you
Reliability / uptime Your on-call rotation The provider's problem
Ongoing cost Eng-weeks + maintenance + VM/infra Usage-based (often a free tier)
Best when You need infra control or on-prem You want to ship the product, not the plumbing

The Free managed option: Metered Realtime Messaging

If "buy" is the right side of that table for you, Metered Realtime Messaging is a managed, high-availability WebSocket service that hands you rooms, presence, direct messages, and auth over the same JSON-over-WebSocket model we just built — minus the operations.

It's deliberately raw JSON over WebSocket, not Socket.io (so clients on any stack — browser, Node, Go, Python, Swift — can speak the wire protocol), and the browser SDK is MIT-licensed, zero-dependency, and ~13 KB gzipped. Here's the rooms-and-presence app from above, as a client against the managed service — no server for you to run:

So here's the payoff. This is the entire managed version of the chat app we just built — same UI, same room, same presence. Notice what's missing: there is no server file.

Save this as metered-chat.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>WebSocket chat demo — managed (Metered Realtime)</title>
    <style>
      body { font: 16px/1.5 system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; padding: 0 1rem; }
      #log { border: 1px solid #cbd5e1; border-radius: 8px; height: 16rem; overflow-y: auto; padding: .75rem; margin-bottom: .75rem; }
      #log div { padding: .1rem 0; }
      form { display: flex; gap: .5rem; }
      input { flex: 1; padding: .5rem; border: 1px solid #cbd5e1; border-radius: 8px; }
      button { padding: .5rem 1rem; border: 0; border-radius: 8px; background: #6d5efc; color: #fff; cursor: pointer; }
    </style>
  </head>
  <body>
    <h1>WebSocket chat — managed</h1>
    <div id="log"></div>
    <form id="form">
      <input id="input" autocomplete="off" placeholder="Type a message…" />
      <button type="submit">Send</button>
    </form>

    <script type="module">
      import { SignallingClient } from "https://esm.sh/@metered-ca/realtime@1.1.0";

      const log = (line) => {
        const el = document.getElementById("log");
        el.append(Object.assign(document.createElement("div"), { textContent: line }));
        el.scrollTop = el.scrollHeight;
      };

      // Publishable key — safe to use in the browser. Get yours free (no card):
      // https://www.metered.ca/docs/realtime-messaging/
      const client = new SignallingClient({ apiKey: "pk_live_YOUR_KEY" });

      await client.connect();
      log("· connected");

      client.on("message", ({ channel, from, data }) => {
        log(`peer: ${data.text}`);
      });

      client.on("presence", ({ channel, joined, left }) => {
        for (const peer of joined) log("· peer-joined");
        for (const peer of left) log("· peer-left");
      });

      await client.subscribe("lobby");
      log("· joined room: lobby");

      document.getElementById("form").addEventListener("submit", async (e) => {
        e.preventDefault();
        const input = document.getElementById("input");
        const text = input.value.trim();
        if (!text) return;
        await client.publish("lobby", { text });
        log("you: " + text);
        input.value = "";
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Serve the folder with any static server, open the page in two tabs, and type. rooms-server.js, the heartbeat, the nginx block, the TLS certificates — none of it exists on your side anymore.

This isn't hypothetical: we ran exactly this file against the live service while writing this guide. Two Chrome tabs connected, presence fired in both directions, and messages round-tripped A→B and B→A — with no echo to the sender, the same semantics our DIY server had.

Here's what each path leaves in your repo — and on your pager:

Build (self-host) Free (managed)
Files you ship rooms-server.js + index.html + nginx config metered-chat.html
Server process Yours, running 24/7 None on your side
TLS / wss:// Reverse proxy + certificates Included
Heartbeat / dead sockets You wrote it (and maintain it) Built into the service + SDK
Reconnection Still on your to-do list Automatic — exponential backoff + jitter
Who gets paged You The provider

(Comparison as of 2026-07-13.)

The endpoint is wss://rms.metered.ca/v1; the pk_live_ key is publishable, so a no-backend prototype needs nothing else. For production you mint short-lived JWTs from a secret key on your server. On the performance question that matters for a relay, typical message-relay latency on Metered's global service is p50 ≈ 5 ms and p99 ≈ 20 ms, with a usable connection established in roughly 100–200 ms.

And the reliability half of the trade: Metered Realtime's uptime has historically held at five-nines (99.999%).

One nice touch if you're actually doing WebRTC: the same connection can carry your signaling and auto-deliver Metered TURN credentials in the connection welcome, so you don't wire up a separate TURN-credential fetch. That's a signaling-specific detail covered in the WebRTC signaling guide.

 decision fork: self-host a ws server when you need infrastructure control or on-prem; otherwise use a managed WebSocket service

The hard 95%, handled

Earlier we listed everything the toy server ignores. Here's where each of those items lands on the managed path — this table is the real substance of the "buy" decision, so it's worth being specific.

Toy-server gap (from the iceberg) On Metered Realtime
Reconnection + thundering herd The SDK reconnects for you: exponential backoff with jitter, tunable via ReconnectOptions, and close-code-aware — it won't hammer the service after a terminal error
Auth & permissions Publishable key for prototypes; production mints short-lived JWTs that scope exactly which channels a client may touch (wildcard patterns) and what it may do (publish / subscribe / presence / send)
Backpressure & abuse Per-connection token bucket (100 msg/s sustained, 200 burst), 64 KB frame cap, per-IP connection limits
Dead-socket detection Application-level keepalive + inactivity timeout built into the SDK (default 60 s)
TLS wss:// only — nothing to configure
Multi-box scale + uptime The provider's job — the historical five-nines figure above is what that engineering buys

Two of those deserve a sentence each. The JWT model is the production path: your backend signs a short-lived token that binds a user to a stable peer ID, their allowed channels, and their permissions — and the SDK refreshes it automatically on every reconnect.

And the limits aren't fine print — they're the abuse story. The free tier's limits are large and exist to stop misuse, not to squeeze prototypes into an upgrade.

What developers run on it

One connection, one protocol — but teams point it at very different workloads. These are the three we see most.

Live chat & presence. Subscribe with includeSenderMetadata: true and every message arrives stamped with the sender's verified identity — chat bubbles without a user-lookup round trip. The roster is just the presence events you already saw, and moderation is one REST call from your backend to force-disconnect a peer.

await client.subscribe("room-42", { includeSenderMetadata: true });
client.on("message", ({ from, fromMetadata, data }) => {
  addBubble(fromMetadata?.username ?? from, data.text);
});
Enter fullscreen mode Exit fullscreen mode

AI agents. Agent swarms need exactly what a chat room needs: shared channels to coordinate, direct messages to stream results. An orchestrator publishes subtasks into a workflow channel; each agent streams its tool-call output back as direct messages; who-does-what rides in peer metadata.

await client.subscribe("workflow/build-42");                       // every agent joins the job
await client.publish("workflow/build-42", { task: "summarize" });  // orchestrator fans out work
await client.send(orchestratorPeerId, { chunk });                  // agent streams results back 1:1
Enter fullscreen mode Exit fullscreen mode

IoT & device control. Each device gets a JWT scoped to its own channels (devices/<id>/**), so a compromised sensor can't touch the rest of the fleet. Devices publish telemetry and listen for commands over one socket — and your backend issues those commands with a single authenticated POST, never holding a connection at all.

// backend: server-side publish over REST — no WebSocket needed
await fetch(`https://rms.metered.ca/v1/channels/${encodeURIComponent("devices/dev-17/commands")}/publish`, {
  method: "POST",
  headers: { Authorization: `Bearer ${SK}`, "Content-Type": "application/json" },
  body: JSON.stringify({ data: { action: "reboot" }, from: "control-plane" }),
});
Enter fullscreen mode Exit fullscreen mode

The same primitives cover collaborative apps — cursors and game state, with identity cached from presence and updates throttled to ~30 Hz to stay inside the rate limits — and WebRTC signaling, which the companion guide covers end to end.

For the person deciding rather than building, the short version:

Metered Realtime at a glance
SDK @metered-ca/realtime — MIT, zero dependencies, ~13 KB gzipped
Free tier 100 concurrent connections · 100,000 messages/month · no card
Typical latency p50 ≈ 5 ms · p99 ≈ 20 ms (global service)
Uptime Five-nines historically (a track record, contractual SLA available in the paid plans)
Wire protocol Raw JSON over WebSocket — any language can speak it
Control plane REST: mint tokens, publish server-side, kick peers, query usage
WebRTC extra TURN credentials delivered automatically on connect

Frequently asked questions

How many concurrent connections can one WebSocket server handle?
A single well-tuned Node process can hold tens of thousands of mostly-idle connections; the real ceiling is memory per socket and your message rate, not a hard number. Past one box you need horizontal scale (multiple servers + a shared pub/sub layer), which is where most of the operational work lives.

Is ws a good socket.io alternative?
For most apps, yes. socket.io bundles conveniences like auto-reconnect and rooms, at the cost of a heavier client and a socket.io-specific protocol on both ends; ws is a lean, standards-pure WebSocket you extend as needed. If you want the reconnection and rooms socket.io gives you without running the server at all, that's the managed path above.

Do I need wss:// (TLS)?
In production, yes. Browsers block insecure ws:// from HTTPS pages, so you terminate TLS at a reverse proxy and serve wss://.

Can I build a WebSocket server in Python / Go / Java instead of Node?
Yes — the protocol is language-agnostic. Python's websockets (shown above), Go's gorilla/websocket, and Java/Spring all follow the same accept-and-route shape. The build-vs-buy trade-off is identical regardless of language: the code is easy, the operations aren't.

What's the difference between a WebSocket server and a "WebSocket as a service"?
A WebSocket server is software you run. "WebSocket as a service" (a managed WebSocket service) is that server operated for you — connections, scaling, reconnection, and uptime become the provider's job, and you connect as a client. It's the "buy" side of this article.

What can you build on a managed WebSocket service?
Anything the DIY server would carry: live chat and presence, AI-agent coordination, IoT telemetry and device control, collaborative cursors and game state, and WebRTC signaling. The patterns above are the same three primitives — channels, presence, direct messages — pointed at different workloads.

Is a WebSocket server the same as a WebRTC signaling server?
No, but they're related: WebRTC signaling usually runs over a WebSocket server. The signaling server is a specific use of the pattern here — relaying SDP/ICE between peers. See the companion WebRTC signaling guide.

Get the complete code

The full runnable demo — server.js, rooms-server.js, server.py, index.html, the serverless metered-chat.html, and a README — is in the companion repo: github.com/jamesbordane57/websocket-server-demo.

GitHub logo jamesbordane57 / websocket-server-demo

Minimal WebSocket servers in Node.js (ws) + Python: broadcast, rooms, presence, heartbeat — companion code for the WebSocket Server tutorial

Minimal WebSocket server (Node.js + ws)

Companion code for the tutorial "WebSocket Server: How to Build One in Node.js — and When to Use a Managed One."

📖 Full tutorial: <article-url-pending>

Servers, from simplest to slightly-less-simple:

  • server.js — the smallest useful server: relays every message to all other connected clients.
  • rooms-server.js — adds rooms (channels), presence (peer-joined / peer-left), a small JSON protocol, and a ping/pong heartbeat that reclaims dead connections.
  • server.py — the same broadcast server in Python (websockets library), to show the pattern is language-agnostic.

Plus index.html — a tiny browser chat client that talks to rooms-server.js.

Run it

npm install

# broadcast server:
npm start
# …or the rooms + presence + heartbeat server:
npm run rooms
Enter fullscreen mode Exit fullscreen mode

Or the same broadcast server in Python:

pip install websockets
python server.py
Enter fullscreen mode Exit fullscreen mode

Then open index.html in two browser tabs (from file:// is fine, or npm run

git clone https://github.com/jamesbordane57/websocket-server-demo.git
cd websocket-server-demo
npm install
npm start          # broadcast server
npm run rooms      # rooms + presence + heartbeat server
Enter fullscreen mode Exit fullscreen mode

Wrapping up

Building a WebSocket server in Node.js is genuinely easy — fifteen lines for broadcast, fifty for rooms, presence, and a heartbeat, and you saw both run (plus the same thing in Python). What's not easy is everything that keeps one alive under real traffic: reconnection, auth, backpressure, multi-box scale, and five-nines reliability. Then you watched the same app run again with the server deleted.

So make the call deliberately. If you need infrastructure control or on-prem, run your own — you now have the starting point.

If you'd rather ship your product than operate socket servers, connect to a managed one and move on. You can start free with Metered Realtime Messaging and skip the 95%.

Top comments (1)

Collapse
 
alakkadshaw profile image
alakkadshaw

Thank you for reading I hope you like the article