A small CLI WebSocket server and client for real-time broadcast messaging. Start a server, connect as many clients as you like from other terminals, and every message a client sends is instantly relayed to all the others. It works like a stripped-down chat room.
Built as a solution to the Broadcast Server project on roadmap.sh, to learn how real-time features such as chat apps and live scoreboards work under the hood.
$ broadcast-server connect --name alice
Connected as alice (2 online). Type a message and press Enter, /quit to leave.
> hey bob!
> [10:44:05 PM] bob: hi alice 👋
> * carol joined (3 online)
- Single CLI with two commands:
startruns the server andconnectopens an interactive client. - Broadcasts every message to all other connected clients, with sender name and timestamp.
- Join and leave notifications that include the live online count.
- Handles disconnects gracefully. Clients are removed from the list as soon as they leave or drop.
- Heartbeats (ping/pong every 30s) detect and remove dead, half-open connections.
- Graceful shutdown. On Ctrl+C or SIGTERM the server stops accepting connections, closes every client with a "going away" frame, and waits for them before exiting. A second Ctrl+C forces exit.
- Clear error handling for a port already in use, a server that isn't running, invalid arguments, binary frames and oversized payloads.
- One runtime dependency (
ws).
- Node.js 20 or newer
git clone https://github.com/<your-username>/broadcast-server.git
cd broadcast-server
npm install
npm link # makes the `broadcast-server` command available globallyIf you'd rather not link it globally, replace broadcast-server with node bin/broadcast-server.js in the examples below.
broadcast-server start [--port <port>] [--host <host>]
broadcast-server connect [--port <port>] [--host <host>] [--name <name>]
| Option | Description | Default |
|---|---|---|
-p, --port |
Port to listen on (start) or connect to (connect) |
$PORT or 8080 |
--host |
Interface to bind (start) or server host (connect) |
all interfaces / localhost |
-n, --name |
Display name shown to other clients | user-<id> |
-h, --help |
Show help |
Terminal 1: start the server.
broadcast-server start
# Broadcast server listening on ws://localhost:8080Terminals 2, 3, …: connect clients.
broadcast-server connect --name alice
broadcast-server connect --name bobType a message and press Enter to broadcast it. Leave with /quit, Ctrl+C or Ctrl+D. Stop the server with Ctrl+C and every client is notified and disconnected cleanly.
┌──────────── broadcast-server ────────────┐
alice ──ws──▶│ clients: Map<WebSocket, {id, name}> │──ws──▶ bob
│ on message → send to every other client │──ws──▶ carol
└──────────────────────────────────────────┘
- Connect. The server stores each new connection in a
Maptogether with its id and name, sends it awelcome, and tells everyone else itjoined. - Message. Text a client sends is trimmed, wrapped with sender and timestamp, and sent to every other open connection. The sender already sees what they typed, so it isn't echoed back.
- Disconnect. When a socket closes (cleanly or not), it is removed from the map and the others get a
leaveevent. - Heartbeat. Every 30s the server pings each client. Any client that didn't answer the previous ping is terminated. This catches connections that died without a close frame (network loss, sleeping laptop).
- Shutdown.
close()stops the listener, sends close code1001 Going Awayto every client, force-terminates stragglers after 3s, and resolves once everything is closed.
Clients send plain text frames. The server sends JSON frames:
type |
Fields | When |
|---|---|---|
welcome |
id, name, online |
Sent to a client right after it connects |
join |
name, online |
Another client connected |
leave |
name, online |
Another client disconnected |
message |
from, text, timestamp |
Another client sent a message |
error |
text |
The client sent something invalid (e.g. a binary frame) |
The client name is passed as a query parameter (ws://localhost:8080?name=alice), so any WebSocket client can join. For example, with wscat:
npx wscat -c "ws://localhost:8080?name=wscat"npm testThe tests use Node's built-in test runner. They start real servers on random ports and cover broadcasting, join/leave events, disconnect cleanup, heartbeat timeouts, graceful shutdown, port conflicts and the interactive client.
bin/broadcast-server.js CLI entry point (argument parsing, signal handling)
src/server.js WebSocket server: client registry, broadcast, heartbeat, shutdown
src/client.js Interactive terminal client
test/ node:test suites