Rooms let you group clients so messages only go to people in that group — like a group chat, a game lobby, or a collaborative document. The ws library has no built-in rooms, so you build them yourself with a Map of Sets.


Room Data Structure

// rooms = Map< roomName: string, clients: Set<WebSocket> >
const rooms = new Map();

Core Room Helpers

// server.js
const { WebSocket } = require('ws');

const rooms = new Map();

// ── Join a room ────────────────────────────────────────
function joinRoom(ws, roomName) {
  if (!rooms.has(roomName)) {
    rooms.set(roomName, new Set());
  }
  rooms.get(roomName).add(ws);

  // Track which rooms this socket is in
  ws.currentRooms = ws.currentRooms || new Set();
  ws.currentRooms.add(roomName);

  console.log(`${ws.clientId} joined '${roomName}' (${rooms.get(roomName).size} members)`);
}

// ── Leave a room ───────────────────────────────────────
function leaveRoom(ws, roomName) {
  rooms.get(roomName)?.delete(ws);
  ws.currentRooms?.delete(roomName);

  // Clean up empty rooms
  if (rooms.get(roomName)?.size === 0) {
    rooms.delete(roomName);
    console.log(`Room '${roomName}' deleted (empty)`);
  }
}

// ── Broadcast to a room (excluding sender) ─────────────
function toRoom(roomName, data, excludeWs = null) {
  const room = rooms.get(roomName);
  if (!room) return;

  room.forEach((client) => {
    if (client !== excludeWs && client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(data));
    }
  });
}

// ── Get room info ──────────────────────────────────────
function getRoomInfo(roomName) {
  return {
    name:    roomName,
    members: rooms.get(roomName)?.size ?? 0,
  };
}

Message Handler

wss.on('connection', (ws) => {
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);

    switch (msg.type) {
      case 'join':
        joinRoom(ws, msg.room);
        // Notify everyone in room that someone joined
        toRoom(msg.room, {
          type: 'system',
          text: `${ws.username} joined the room`,
          room: msg.room,
        }, ws);
        // Confirm to the joining client
        ws.send(JSON.stringify({
          type: 'joined',
          room: msg.room,
          info: getRoomInfo(msg.room),
        }));
        break;

      case 'leave':
        leaveRoom(ws, msg.room);
        toRoom(msg.room, {
          type: 'system',
          text: `${ws.username} left the room`,
        });
        break;

      case 'message':
        toRoom(msg.room, {
          type: 'message',
          text: msg.text,
          from: ws.username,
          room: msg.room,
          timestamp: Date.now(),
        }, ws); // exclude sender
        break;
    }
  });

  // On disconnect — leave ALL rooms this socket was in
  ws.on('close', () => {
    ws.currentRooms?.forEach((roomName) => {
      leaveRoom(ws, roomName);
    });
  });
});

Client-Side Room Usage

// client.js
const ws = new WebSocket('ws://localhost:8080');

ws.onopen = () => {
  // Join a room
  ws.send(JSON.stringify({
    type: 'join',
    room: 'gaming',
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'message') {
    console.log(`[${data.room}] ${data.from}: ${data.text}`);
  }
  if (data.type === 'system') {
    console.log(`â„šī¸ ${data.text}`);
  }
  if (data.type === 'joined') {
    console.log(`Joined '${data.room}' — ${data.info.members} members`);
  }
};

// Send a message to a room
function sendToRoom(room, text) {
  ws.send(JSON.stringify({ type: 'message', room, text }));
}

// Leave a room
function leaveRoom(room) {
  ws.send(JSON.stringify({ type: 'leave', room }));
}

Multiple Rooms at Once

A single client can be in multiple rooms simultaneously. The ws.currentRooms Set tracks this:

// Client joins multiple rooms
ws.send(JSON.stringify({ type: 'join', room: 'gaming' }));
ws.send(JSON.stringify({ type: 'join', room: 'announcements' }));
ws.send(JSON.stringify({ type: 'join', room: 'team-alpha' }));

// On disconnect the server automatically cleans up all 3 rooms

Want rooms with zero boilerplate? Socket.io gives you socket.join('room') and io.to('room').emit() built-in. See Chapter 9.