We covered Rooms in the last post. Namespaces are a related idea, but they solve a different problem. This post explains what namespaces are, how they differ from rooms, and when to actually use them.
What Is a Namespace?
A namespace is a way to split your application logic over a single underlying connection, by assigning it its own communication path. Every Socket.IO server actually already has one namespace by default, called the root namespace, written as /. Every example we have built so far, using io.on("connection", ...), has really been using this default namespace all along, without us naming it explicitly.
Namespaces let you create additional, separate paths, for example /chat, /notifications, or /admin, each with its own set of connected clients and its own event handlers. Even though they are logically separate, all namespaces still share the same underlying transport connection, so you are not paying the cost of multiple physical connections.
Namespaces vs. Rooms: The Key Difference
This is where beginners often get confused, so let's be direct about it:
- A room is a subdivision inside a namespace, purely for grouping sockets. A client does not explicitly "connect" to a room, the server puts it there using
socket.join(). - A namespace is a separate communication channel entirely, and the client explicitly connects to it by specifying its path, similar to choosing a different URL.
A useful way to remember it: namespaces separate different parts of your application. Rooms group clients within one part of your application. A chat namespace might still use rooms internally, for example, one room per chat channel, while the /admin namespace stays completely separate and unaffected by anything happening in /chat.
Creating a Namespace on the Server
You create a namespace using io.of(path):
const chatNamespace = io.of("/chat");
chatNamespace.on("connection", (socket) => { console.log("A client connected to /chat:", socket.id);
socket.on("chatMessage", (data) => { chatNamespace.emit("newMessage", data); }); });
Notice that inside a namespace, you use the namespace object itself (chatNamespace) instead of io, when you want to broadcast to everyone connected to that specific namespace. chatNamespace.emit(...) only reaches clients connected to /chat, nobody connected to the default / namespace or any other namespace receives it.
Connecting to a Namespace From the Client
On the client, you specify the namespace path when creating the connection:
import { io } from "socket.io-client";
const chatSocket = io("/chat", { path: "/api/socket", });
Two different paths are involved here, and it's important not to confuse them:
/api/socketis the Engine.IO transport path, the low-level endpoint the actual connection travels through, which we configured back in Phase 2/chatis the Socket.IO namespace, a logical separation on top of that same connection
Both are needed together, and they serve completely different purposes.
A Practical Example: Separating Chat and Admin
Let's say your app has a normal chat area for regular users, and a separate admin monitoring panel. Instead of mixing this logic into one giant connection handler with manual permission checks everywhere, namespaces let you cleanly separate it:
const chatNamespace = io.of("/chat"); const adminNamespace = io.of("/admin");
chatNamespace.on("connection", (socket) => { socket.on("chatMessage", (data) => { chatNamespace.emit("newMessage", data); }); });
adminNamespace.on("connection", (socket) => { socket.on("banUser", (userId) => { console.log("Admin requested ban for:", userId); }); });
A regular user connecting to /chat has no way of sending or receiving anything happening on /admin, and vice versa. This gives you a clean separation of concerns, without needing to check "is this an admin event?" inside every single handler.
Restricting Access to a Namespace
Since regular users should not be able to freely connect to /admin, namespaces support their own middleware, using .use(), which runs before a connection is accepted:
adminNamespace.use((socket, next) => { const isAdmin = socket.handshake.auth.role === "admin";
if (isAdmin) { next(); } else { next(new Error("Not authorized")); } });
If next() is called with an error, the client's connection attempt to that namespace is rejected, and it never reaches the connection event at all. We will cover authentication properly, including socket.handshake.auth, in Phase 5, but this shows how namespaces naturally support access control at the connection level itself.
Dynamic Namespaces
Socket.IO also supports creating namespaces dynamically, using a regular expression, useful when you don't know your namespace names in advance, for example, one namespace per project or per organization:
io.of(/^\/room-\d+$/).on("connection", (socket) => { const namespaceName = socket.nsp.name; console.log("Connected to dynamic namespace:", namespaceName); });
That said, dynamic namespaces are not common practice for most applications. In the vast majority of cases, especially something like separate chat channels, Rooms are the better and simpler tool for the job, since creating a new namespace for every single chat channel would be unnecessarily heavy. Reach for namespaces when you need to separate distinct parts of your application's logic, not when you just need to group users.
Applying This to Our Chat App
For our course project, we do not need multiple namespaces, our chat app is simple enough that the default namespace with rooms, as we built in the last post, is the right tool. This post is here so you recognize namespaces when you see them in other projects, and know exactly when reaching for one makes sense over a room.
Summary
- A namespace is a separate communication path on the same underlying connection, created using
io.of(path)on the server, and connected to usingio(path, ...)on the client - Namespaces separate distinct parts of an application; rooms group clients within one part of an application
- Broadcasting inside a namespace uses the namespace object itself, not
io, to avoid reaching clients in other namespaces - Namespaces support their own middleware through
.use(), useful for restricting access, such as an admin-only namespace - Dynamic namespaces exist for advanced cases, but rooms are usually the better choice for grouping, such as chat channels
In the next post, we look at broadcasting in more detail, covering how to send messages to everyone, or to specific users and rooms, tying together everything from events, rooms, and namespaces into clear broadcasting patterns.
No comments:
Post a Comment