ws Library
Build production WebSocket servers in Node.js with the ws library, including heartbeat, auth, and graceful shutdown.
Search across all documentation pages
Build production WebSocket servers in Node.js with the ws library, including heartbeat, auth, and graceful shutdown.
Quick-reference recipe card - copy-paste ready.
import { WebSocketServer, WebSocket } from "ws";
import { createServer } from "node:http";
const server = createServer();
const wss = new WebSocketServer({ noServer: true });
server.on("upgrade", (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
});
wss.on("connection", (ws) => {
ws.on("message", (data) => ws.send(data));
ws.on("close", () => console.log("Client disconnected"));
});
server.listen(3000);When to reach for this: You need raw WebSocket control without Socket.IO overhead, or NestJS/Fastify WebSocket gateway.
import { WebSocketServer, WebSocket } from "ws";
import { createServer } from "node:http";
const server = createServer();
const wss = new WebSocketServer({ noServer: true });
function heartbeat() {
// @ts-expect-error custom property
this.isAlive = true;
}
server.on("upgrade", (req, socket, head) => {
const url = new URL(req.url ?? "/", "http://localhost");
const token = url.searchParams.get("token");
if (token !== process.env.WS_TOKEN) {
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
socket.destroy();
return;
}
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
});
wss.on("connection", (ws: WebSocket) => {
ws.isAlive = true;
ws.on("pong", heartbeat);
ws.on("message", (raw) => {
const msg = JSON.parse(String(raw));
if (msg.type === "ping") {
ws.send(JSON.stringify({ type: "pong" }));
return;
}
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: "broadcast", data: msg }));
}
});
});
ws.on("close", () => console.log("Client left"));
});
// Heartbeat every 30s
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) { ws.terminate(); return; }
ws.isAlive = false;
ws.ping();
});
}, 30_000);
wss.on("close", () => clearInterval(interval));
server.listen(3000);What this demonstrates:
terminate() for unresponsive clientsws implements the WebSocket protocol (RFC 6455)noServer: true lets you handle upgrade manually (for auth)ping()/pong() are protocol-level frames (not application messages)wss.clients is a Set of all connected WebSocket instancesconst ws = new WebSocket("ws://localhost:3000?token=secret");
ws.onopen = () => ws.send(JSON.stringify({ type: "hello" }));
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onclose = () => console.log("Disconnected");@WebSocketGateway({ path: "/ws" })
export class EventsGateway {
@SubscribeMessage("message")
handleMessage(@MessageBody() data: string) {
return { event: "response", data };
}
}handleUpgrade.wss.clients forever. Fix: ping/pong interval with terminate().close on server shutdown - clients get RST. Fix: notify clients, then wss.close().close.| Alternative | Use When | Don't Use When |
|---|---|---|
| Socket.IO | Need rooms, fallback, Redis adapter | Want minimal protocol overhead |
| SSE | Server-to-client only | Bidirectional messaging |
| uWebSockets.js | Maximum WebSocket throughput | Simpler ws API preferred |
| NestJS Gateway | NestJS app with decorators | Non-NestJS project |
ws is raw WebSocket. Socket.IO adds rooms, namespaces, fallback, and Redis adapter. Use ws when you want control; Socket.IO when you want features.
Track connections in a Map keyed by IP during upgrade. Reject when limit exceeded.
Yes via @fastify/websocket plugin, which wraps ws internally.
ws.send(buffer) with Buffer or ArrayBuffer. Useful for file chunks or protobuf.
Terminate TLS at the proxy (same as HTTPS). Client connects to wss:// which proxies to ws:// on Node.
10k-50k idle per process. Depends on message rate and fanout pattern. Profile your workload.
Use ws WebSocket client in tests, or websocket npm package. Connect to server.address() port.
JSON for simplicity. Binary (protobuf, msgpack) when message volume or size demands it.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026