This episode builds a WebSocket server with the ws library: installation, server creation, the connection and message events, broadcasting to all clients, and connection management with identification and cleanup.

In episode 4 you got a client. Now it is time to build the serving side: the WebSocket server. We use ws, the most popular Node.js library for this protocol.
The ws library is lightweight and close to the raw protocol — no automatic reconnection layer or rooms. That is precisely its advantage: you will understand how WebSocket actually works before layering on the convenience of Socket.IO in the next episode. In this episode you will create a server, handle events, broadcast messages, and manage many clients.
Start from an empty project:
mkdir ws-server
cd ws-server
npm init -y
npm install wsThe command npm install ws pulls in the library along with its minimal dependencies. After this, you are ready to write your first server.
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
console.log("klien terhubung");
ws.send("selamat datang");
});new WebSocketServer({ port: 8080 }) creates a server that listens for connections on port 8080. The connection event is fired every time a handshake completes, carrying the ws object belonging to that client.
The server communicates through events. Here are the handlers you must know:
wss.on("connection", (ws) => {
ws.on("message", (data, isBinary) => {
console.log("terima:", data.toString());
ws.send("pesan diterima");
});
ws.on("close", () => {
console.log("klien pergi");
});
ws.on("error", (err) => {
console.error("error:", err.message);
});
ws.on("pong", () => {
console.log("pong diterima");
});
});ws.on("message", handler) is called for every data frame. The isBinary argument distinguishes text and binary frames. The pong event is only active if the server uses a heartbeat — covered in a later episode.
Because ws can receive two data types:
ws.on("message", (data, isBinary) => {
if (isBinary) {
console.log("data biner:", data);
} else {
console.log("teks:", data.toString());
}
});Checking isBinary determines how to interpret data, which in ws is a Buffer for binary and a Buffer or string for text.
To send to a specific client, you need to keep a reference to it:
const wss = new WebSocketServer({ port: 8080 });
const klien = new Map();
wss.on("connection", (ws) => {
klien.set(ws, new Date());
ws.on("message", (data) => {
const pesan = data.toString();
for (const [k] of klien) {
k.send(pesan);
}
});
});Looping for (const [k] of klien) and calling k.send(pesan) on every entry is manual broadcasting. Since there is no broadcast API in ws, you build it yourself — exactly what Socket.IO will make easier.
Without cleanup, already-disconnected clients will sit idle in memory:
ws.on("close", () => {
klien.delete(ws);
console.log("klien tersisa:", klien.size);
});Always call klien.delete(ws) in the close event. The size of klien.size is also useful as a metric for active connection counts — the seed of the monitoring covered in a later episode.
WebSocketServer accepts various options:
port or server: a standalone port or an existing HTTP server.maxPayload: the payload size limit, for example 100e6 for 100MB.perMessageDeflate: enables permessage-deflate compression.clientTracking: tracks the list of clients in wss.clients.Combining it with an HTTP server brings a big advantage: a single port serves both the REST API and WebSocket.
const http = require("http");
const { WebSocketServer } = require("ws");
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end("server jalan");
});
const wss = new WebSocketServer({ server });
wss.on("connection", (ws) => {
ws.send("halo dari port yang sama");
});
server.listen(8080);With new WebSocketServer({ server }), one HTTP server on port 8080 serves both. This is the pattern production applications use.
Tip
Always set maxPayload. Without a limit, a client can send a giant payload and eat up server memory — one of the DoS vectors we will discuss in a later episode.
Episode 5 gave you a functional WebSocket server: ws installation, server creation, handling the connection and message events, manual unicast and broadcast, and client-list management that stays free of memory leaks.
Key takeaways:
In the next episode we introduce Socket.IO — the library that layers WebSocket with automatic reconnection, fallback, rooms, and namespaces. You will see when to use Socket.IO and when to stick with plain ws.