#13 Implementing socket.io rooms
- Aaron
- Dec 4, 2020
- 5 min read
Updated: Dec 5, 2020
At long last, I think I'm finally getting the hang of implementing socket.io rooms. Because I found the online documentation lacking, I'll try to share with you what I've learned.

Quick things to note:
I am using socket.io v3.0.3
Get a list all created rooms socket.adapter.rooms or Object.keys(io.sockets.adapter.rooms) It used to be io.sockets.manager for older socket.io versions => but that does not work anymore (use socket.adapter.rooms now)
Socket.io rooms are destroyed when they are empty, and when a user joins a room that does not exist yet, they will create a new room
Socket.io will automatically create a default room for each connection. This can be handy for direct private messaging, but if you, like me, need a more dynamic implementation of rooms (users can create rooms themselves or see all of the available game rooms they can join), means you can’t depend on the socket.io list of all created rooms alone. You will need to create a room object and manage it yourself (handle leaves, joins, sync it with socket.io own rooms implementation, …). Something I only figured out after quite a bit of struggling. I really wish the documentation of socket.io would’ve addressed this. Either in documentation or a getting starter project. So I would’ve known from the get-go that I was not supposed to try and manage it with socket.io’s implementation alone.
You can add your own attributes to the socket instance (socket.youCustomAttribute = attributeValue)
The emit cheat sheet is your one and only friend https://socket.io/docs/v3/emit-cheatsheet/
My rooms requirements for this project:
Login (with a unique name)
Go to the second page / render new components
Send data (to server, from server to everyone but sender...
Show currently connected participants. In general, in rooms, ...
Create a room
Put a check on user created rooms, don’t let them create rooms like crazy.
Uniquely identifiable
Capable of holding data (users, gameState)
View rooms (view the available rooms in real-time)
Join room (Join an available room)
Destroy empty rooms
capable of handling game logic and closed off from other rooms
And you've already read through the basics of socket.io's rooms documentation.
Socket.io rooms are destroyed when they are empty, useful right? It could've been, for my use case scenario where you want to create "game rooms" exclusively, you get hindered by the fact that socket.io also creates a default room for each user (this was meant to facilitate private messaging, so you could just send a message to that user's private room). So if we would just show a list with all rooms to our users, they would see all those private default rooms too. That's why we need to create and manage our own room object and handle the leaves, joins, creation, ... and sync it with socket.io's own implementation of rooms.
I did not include some parts of the code, to keep the focus more on the core, and keep it simpler. If you want to see the full code: check my Github.
Login
I wanted a quick way to log the users in, just making sure that the username they choose was unique and that there aren't too many weird characters in there.
You'll probably want to have a form somewhere in your front end where you want to put a login event emitter on:
When someone fills out the name form, you'll want to emit that event to the server.
socket.emit("name", `enteredName`);
On the server, you'll want to listen for that event:
//init connected clients, so that you can keep track of them
const clients = {};
//link name
socket.on("name", name => {
//validate the name
if (name) {
// Put your name validation here (no unwated characters, ...)
} else {
return;
}
// check if name is not already in use
let nameInUse = false;
for (const socketId in clients) {
if (clients.hasOwnProperty(socketId)) {
const otherClient = clients[socketId];
if (otherClient.name === name) {
nameInUse = true;
}
}
}
if (nameInUse) {
socket.emit("name-error", "Name is already in use");
return;
}
//Pass name to clients object and show users
clients[socket.id].name = name;
// You can also add an username attribute to the socket connection instance, so you can easily access the username
socket.username = name;
// Pass info to the client that creation was successful
socket.emit("name", clients[socket.id]);
Create a room
Once the user is logged in, they should be able to create a room, the server code could look a bit like this:
//init your own room object outside io.on("connection"), so we can manage the rooms for ourselves
const rooms = {};
/*--- SOCKET.IO ---*/
//create a room
socket.on("createRoom", () => {
//stop creation of extra rooms when user is already in one, or if user is not logged in (we will create .connectedRoom later, once we have actually created that room)
if (socket.connectedRoom || !socket.username) {
return;
}
//get a randomRoomName (the GetRandom.randomRoomName is a javascript function I created that gives me a random unique roomName. It takes the rooms object with all the current rooms to make sure it has no duplicate value)
roomName = GetRandom.randomRoomName(rooms);
//if the roomName does not exist, socket.join will create a new room
socket.join(roomName);
//socket.rooms will contain multiple rooms because each socket automatically joins a room identified by its id as a default room
console.log(socket.rooms); //[default room, room we created]
//add it to our own room object and pass an empty default users object
rooms[roomName] = { name: roomName, users: {} };
//add creator of the room to the room by default
rooms[roomName].users[socket.id] = socket.username;
//create a rooms property to keep track of user rooms
socket.connectedRoom = rooms[roomName];
clients[socket.id].connectedRoom = rooms[roomName];
//emit an event that we created a room to the creator
//this should redirect the user to the created room in front-end
socket.emit("createdRoom", roomName);
//because a new room was created, send an event with updated rooms
io.emit("allRooms", rooms);
});
Join a room
Side-note: I originally had a "remove all rooms"-function that I ran before I'd let the user join another room. Because socket.io also creates a default room for each user (this was meant to make private messaging easier), it would also remove that default room, and therefore the entire server would crash. So please be careful when handling rooms.
//join a room
socket.on("joinRoom", roomName => {
//check if room already exsists and if user is logged in
if (roomName in rooms && socket.username) {
//check if user is already connected to another room
if (socket.connectedRoom && socket.connectedRoom.name !== roomName) {
console.log(`already connected to another room: ${
Object.values(socket.connectedRoom)[0]
}`);
//leave previous connected room with socket.io
socket.leave(socket.connectedRoom.name);
//delete from our own rooms object
delete rooms[socket.connectedRoom.name].users[socket.id];
//if the previous connected room is empty, delete the room
if (Object.keys(rooms[socket.connectedRoom.name].users).length === 0) {
delete rooms[socket.connectedRoom.name];
//because a room was deleted, send an updated rooms event
io.emit("allRooms", rooms);
}
//delete our connectedRoom attribute
delete socket.connectedRoom;
//delete from our clients object
delete clients[socket.id].connectedRoom;
io.emit("allRooms", rooms);
}
//join a new room
socket.join(roomName);
console.log(`${socket.username} joined ${roomName}`);
//add joined user to our rooms object
rooms[roomName].users[socket.id] = socket.username; //{ room1: { name: 'room1', users: { id: 'username' } } }
io.to(roomName).emit(
"roomEntered",
`${socket.username} has joined ${roomName}`);
//set our connectedRoom to the room we just joined
socket.connectedRoom = rooms[roomName];
clients[socket.id].connectedRoom = rooms[roomName];
socket.emit("joinedRoom", roomName);
} else {
return socket.emit("err", `Can't find room ${roomName}`);
}
io.emit("allRooms", rooms);
});
Leaving rooms on disconnect
//remove id if client disconnected
socket.on("disconnect", () => {
//remove rooms that client was in (with my own custom getUserRooms function)
getUserRooms(socket).forEach(roomName => {
//delete user from rooms object
delete rooms[roomName].users[socket.id];
//delete room if there are 0 users
if (Object.keys(rooms[roomName].users).length === 0) {
delete rooms[roomName];
//because a room was deleted, send an updated rooms event
io.emit("allRooms", rooms);
}
});
//delete user from clients
delete clients[socket.id];
calcConnectedClients();
});
These code snippets should help to get you started! ✌️
Comments