Tier 3 — Upper-Intermediate

Chat Server (Sockets)

Build the broadcast core of a multi-client chat server — a hub that fans each message out to every other connected client. The heart of every real-time app.

🐍 Python 🟨 JavaScript

1. The Problem

Build the message-routing core of a chat server: a hub tracks connected clients, and when one client sends a message the hub broadcasts it to every other client (not back to the sender). Clients can join and leave at any time.

Why this project? Every real-time app — chat, multiplayer games, live dashboards — has this pub/sub hub at its core. The networking (sockets) is plumbing; the interesting logic is who receives what.

2. How to Think About This

Model each client as an object with a send() method and keep a list of connected clients in the hub. broadcast(message, sender) loops the clients and delivers to everyone except the sender. Join/leave just add/remove from the list. In a real server each client is a live TCP or WebSocket connection and send() writes to that socket — but the routing logic is identical, which is why we can build and test it without any networking.

3. The Build

chat.mjs
// A client: in a real server this wraps a live socket; here .send()
// just records what the client received so we can see the routing.
export class Client {
  constructor(name) { this.name = name; this.inbox = []; }
  send(message) { this.inbox.push(message); }
}

export class ChatHub {
  constructor() { this.clients = []; }

  join(client)  { this.clients.push(client); }
  leave(client) { this.clients = this.clients.filter(c => c !== client); }

  // Deliver a message to everyone EXCEPT the sender.
  broadcast(message, sender) {
    for (const client of this.clients) {
      if (client !== sender) client.send(message);
    }
  }
}

// Demo
const hub = new ChatHub();
const alice = new Client("alice");
const bob = new Client("bob");
const carol = new Client("carol");
[alice, bob, carol].forEach(c => hub.join(c));

hub.broadcast("hello everyone", alice);
console.log("bob heard:", bob.inbox);       // [ 'hello everyone' ]
console.log("carol heard:", carol.inbox);   // [ 'hello everyone' ]
console.log("alice heard:", alice.inbox);   // [] (sender excluded)

4. Test & Prove

chat.test.mjs
import { ChatHub, Client } from "./chat.mjs";

let pass = 0, fail = 0;
function test(desc, fn) {
  try { fn(); console.log("  \u2713", desc); pass++; }
  catch (e) { console.log("  \u2717", desc, "\u2014", e.message); fail++; }
}
function assert(cond, msg) { if (!cond) throw new Error(msg || "assertion failed"); }

test("a message reaches every other client", () => {
  const hub = new ChatHub();
  const a = new Client("a"), b = new Client("b"), c = new Client("c");
  [a, b, c].forEach(x => hub.join(x));
  hub.broadcast("hi", a);
  assert(b.inbox[0] === "hi" && c.inbox[0] === "hi");
});
test("the sender does not receive its own message", () => {
  const hub = new ChatHub();
  const a = new Client("a"), b = new Client("b");
  [a, b].forEach(x => hub.join(x));
  hub.broadcast("hi", a);
  assert(a.inbox.length === 0);
});
test("a client who left stops receiving", () => {
  const hub = new ChatHub();
  const a = new Client("a"), b = new Client("b");
  [a, b].forEach(x => hub.join(x));
  hub.leave(b);
  hub.broadcast("hi", a);
  assert(b.inbox.length === 0);
});
test("messages accumulate in order", () => {
  const hub = new ChatHub();
  const a = new Client("a"), b = new Client("b");
  [a, b].forEach(x => hub.join(x));
  hub.broadcast("one", a);
  hub.broadcast("two", a);
  assert(JSON.stringify(b.inbox) === JSON.stringify(["one", "two"]));
});

console.log(`\n${pass} passed, ${fail} failed`);

5. The Interface

node chat.mjs

bob heard: [ 'hello everyone' ]
carol heard: [ 'hello everyone' ]
alice heard: []

6. Run It

node chat.mjs
node chat.test.mjs

# The full networked server uses Node's net or ws module to accept real
# socket connections; the broadcast/join/leave logic above is unchanged.

🎯 Try this next

  1. Wire it to Node\u2019s ws (WebSocket) module so real browsers can connect
  2. Add named rooms/channels so a message only reaches one room
  3. Add usernames and prefix each broadcast with who sent it
  4. Add private (direct) messages between two named clients

What you practised

The pub/sub broadcast pattern at the heart of every real-time app, tracking connected clients, and excluding the sender — the routing logic that networking merely delivers.

Reference: Classes · Arrays · Servers

Try It Yourself