Learn WebSocket - Building Your First Real-Time Application
Episode 7 of 34

Learn WebSocket - Building Your First Real-Time Application

This episode builds your first real-time application: a simple chat with a ws server, an HTML UI, join and leave notifications, an online user list, and debugging via DevTools.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Now it is time to tie everything you have learned into one piece: your first real-time application. We build a simple chat with a ws server in Node.js, an HTML interface, an online user list, and join and leave notifications.

Episode 7 is not just about writing code — you also learn to debug a real-time application via DevTools: inspecting WebSocket frames, monitoring the connection state, and tracing messages. By the end of the episode you have an app you can open in several tabs at once and watch messages travel between tabs in real time.

Project Structure

File Organization

Chat project structure
chat-app/
  server.js
  public/
    index.html
    style.css
    app.js

The chat-app/server.js + public/ layout above separates server logic from client assets. The server serves static files and WebSocket on the same port — the pattern already introduced in episode 5.

The Chat Server

Storing User Identity

The server needs to know who sent each message. We store the username as a property of the WebSocket object:

JSChat server with identity
const http = require("http");
const fs = require("fs");
const path = require("path");
const { WebSocketServer } = require("ws");
 
const server = http.createServer((req, res) => {
  const file = path.join(__dirname, "public", req.url === "/" ? "index.html" : req.url);
  fs.createReadStream(file).pipe(res);
});
 
const wss = new WebSocketServer({ server });
 
wss.on("connection", (ws) => {
  ws.on("message", (data) => {
    const pesan = JSON.parse(data.toString());
 
    if (pesan.type === "join") {
      ws.nama = pesan.nama;
      broadcast({ type: "sistem", teks: pesan.nama + " bergabung" });
      kirimDaftar();
    }
 
    if (pesan.type === "chat") {
      broadcast({ type: "chat", nama: ws.nama, teks: pesan.teks });
    }
  });
 
  ws.on("close", () => {
    if (ws.nama) {
      broadcast({ type: "sistem", teks: ws.nama + " keluar" });
      kirimDaftar();
    }
  });
});

Storing ws.nama directly on the connection object keeps the identity attached without an external data structure. All messages are wrapped in JSON with a type field as a marker — the envelope pattern we will dive into in episode 10.

Broadcast and the User List

JSBroadcast and the online list
function broadcast(pesan) {
  for (const klien of wss.clients) {
    klien.send(JSON.stringify(pesan));
  }
}
 
function kirimDaftar() {
  const daftar = [];
  for (const klien of wss.clients) {
    daftar.push(klien.nama);
  }
  broadcast({ type: "daftar", daftar });
}

wss.clients is ws's built-in Set of all active connections — no manual Map needed like in episode 5. broadcast sends objects already serialized with JSON.stringify(pesan).

Client: UI and Logic

Page Structure

The chat page has three parts: the user list, the message list, and the send box.

JSChat HTML structure
<div id="daftar"></div>
<div id="pesan"></div>
<input id="input" placeholder="ketik pesan">
<button id="kirim">Kirim</button>

The structure above separates the online user list, the message list, and the input controls — all populated from JavaScript.

Client Logic with WebSocket

JSChat client logic
const input = document.getElementById("input");
const ws = new WebSocket("ws://localhost:8080");
 
const nama = prompt("Nama kamu?");
ws.onopen = () => {
  ws.send(JSON.stringify({ type: "join", nama }));
};
 
ws.onmessage = (event) => {
  const pesan = JSON.parse(event.data);
  if (pesan.type === "daftar") {
    renderDaftar(pesan.daftar);
  } else {
    renderPesan(pesan);
  }
};
 
function kirim() {
  ws.send(JSON.stringify({ type: "chat", teks: input.value }));
  input.value = "";
}

ws.onopen ensures the join message is sent right after the connection opens. ws.onmessage splits one handler into two paths based on pesan.type.

Additional Features

Join and Leave Notifications

System notifications such as "user joined" and "user left" come from messages of type sistem sent by the server on the connection and close events. The client simply renders the text with a style distinct from ordinary chat messages.

Timestamps and the Typing Indicator

  • Timestamp: the server adds waktu when wrapping the message, sent along with the payload.
  • Typing indicator: the client sends an event of type typing while typing; the server broadcasts it to the other clients without storing it.

Debugging with DevTools

Inspecting WebSocket Frames

Open DevTools in the Network tab, select the WS filter, and click the WebSocket connection. The detail panel shows Messages — every frame sent and received, complete with its timestamp and direction.

Frames in the Network panel
out {type: "join", nama: "arman"}
in  {type: "sistem", teks: "arman bergabung"}
out {type: "chat", teks: "halo semua"}
in  {type: "chat", nama: "arman", teks: "halo semua"}

Watching the out and in frame directions above makes the message flow clear. This is the main debugging tool for WebSocket applications — get used to opening it whenever a message does not arrive.

Common Issues and Fixes

  • The connection closes immediately: check that the URL uses ws in development and wss in production.
  • Messages do not appear: check the message type in the frame — often JSON parsing fails on one side.
  • The user list does not update: make sure the close event calls kirimDaftar.

Closing

Episode 7 brought client and server together into a real chat application: user identity, broadcast, join and leave notifications, an online list, and the ability to debug frames via DevTools.

Key takeaways:

  • A single port can serve static files and WebSocket at the same time.
  • User identity can be stored directly on the connection object.
  • wss.clients is ws's built-in list of active connections.
  • All messages are wrapped in JSON with a type field as a marker.
  • The DevTools Network WS section shows every frame.
  • The three extra features, typing indicator and timestamps, enrich the UX without changing the protocol.

In the next episode we secure this application: authentication and authorization — JWT and session strategies, authentication during the handshake, Socket.IO middleware, room access control, and security best practices.

Learn WebSocket - Building Your First Real-Time Application | Learn WebSocket