Rooms, and Running It
One big room is fine until two conversations want to happen at once. The fix is rooms - named channels where a message only reaches the people in the same channel. This phase adds them, then we run the finished app start to finish and look at the honest next steps if you want to keep going.
By the end you'll have a small but real chat server: named users, join and leave notices, and separate rooms, all running on your machine.
Built on your machine, last time.
How rooms work
A room is a label. Every socket belongs to one room, and broadcasting goes to "everyone in this room" instead of "everyone." You already store state on the socket - socket.username - so you'll add one more field, socket.room, the same way.
The only real change is the broadcast: it now filters by room.
graph TD
A[alice in 'general'] --> S[Server]
B[bob in 'general'] --> S
C[carol in 'random'] --> S
S -- general only --> A
S -- general only --> B
S -- random only --> C
bob and alice hear each other. carol's in another room and hears neither.
Update the server
Open server.js. The join message gains a room, and broadcast gains a room filter:
;
const PORT = 8080;
const wss = ;
;
wss.;
The differences from phase 4:
socket.roomis set on join, defaulting to"general"and clamped to 24 characters, same treatment as the name.broadcasttakes aroomand addsclient.room === roomto its filter. That one clause is the entire room feature - a message only reaches sockets tagged with the matching room.- Join and leave notices go to the room, not the whole server, so people in other rooms aren't bothered by your comings and goings.
This is the whole point of keeping state on the socket: rooms cost you one extra field and one extra condition.
Update the client
The page needs to ask which room. Replace the two lines near the top of the <script> where the username and join are handled. First, the prompts:
const username = ..;
const room = ..;
Then update the open handler to send the room and show it:
socket.;
Everything else in the client stays the same - the message handling and form don't care about rooms, because the server already scoped the messages before they arrived.
Run the whole thing
Here's the complete startup, from a clean terminal:
# Terminal 1 - the server
Leave that running. Then open index.html in your browser - double-click it, or open it however you like - and do it in three tabs.
- Tab 1: name "alice", room "general"
- Tab 2: name "bob", room "general"
- Tab 3: name "carol", room "random"
Now test the boundary. Type in alice's tab: bob sees it, carol does not. Type in carol's tab: nobody in general sees a thing. Each room is its own conversation on the same server. When bob joins, only alice gets the "bob joined #general" notice - carol's window stays quiet.
That's the finished app: a Node WebSocket server broadcasting attributed messages within rooms, and a browser client to use it. You built the whole pipe.
Where to take it next
You've got the core. These are the real directions to extend it, roughly in the order most people want them:
| Want | What it takes |
|---|---|
| History | Right now a fresh tab sees nothing that happened before it joined. Keep the last N messages per room in an array on the server and send them to a client right after it joins. |
| Persistence | That history vanishes when the server restarts. Write messages to SQLite or a file so they survive a restart. |
| A real name form | prompt() is a placeholder. Swap it for an HTML form before connecting - nicer, and you can validate names. |
| Authentication | Anyone can claim any name. Add login (a token the client sends in the join message, checked by the server) so identities are real. |
| Typing indicators | Send a lightweight typing message type, broadcast to the room, and clear it after a short timeout. The message-type pattern you built handles this with no new plumbing. |
| Deploying | Run it on a host so others can connect. You'll serve the HTML over HTTP, put the WebSocket behind the same domain, and switch the client to wss:// (secure WebSocket) since browsers block plain ws:// from HTTPS pages. |
Each of those builds on what's here without rearchitecting. The message-type design from phase 4 is what makes adding features cheap - a new type, a new branch, done.
What you built
A real-time chat server and client, from an empty folder: a server that accepts connections and broadcasts within rooms, a browser client that sends and renders live messages, named users, join and leave announcements, and channels. You wrote every line of the mechanism that makes chat feel instant - the open socket, the fan-out, the per-connection state.
That same shape - keep a connection open, push when something changes, scope it to who should see it - is behind live dashboards, multiplayer games, collaborative editors, and notifications. You now know how it actually works, because you built one.