Advanced Namespace Usage¶
Namespaces are a powerful Socket.IO feature that enables multiplexing multiple communication channels over a single WebSocket connection.
Table of Contents¶
What are Namespaces?¶
Namespaces are logically separated communication channels that share the same physical WebSocket connection. Each namespace:
- Has its own set of events
- Manages its own connection/disconnection
- Can have different handlers
- Shares the underlying TCP/WebSocket connection
Default Namespace¶
The default namespace is /. All events without a specific namespace go here:
// These three code snippets are equivalent
client.On("event", handler)
client.Of("/").On("event", handler)
defaultNs := client.Of("/")
defaultNs.On("event", handler)
Use Cases¶
1. Functional Separation¶
Separate different areas of your application into distinct namespaces:
// Public chat
publicChat := client.Of("/chat")
publicChat.On("message", handlePublicMessage)
// System notifications
notifications := client.Of("/notifications")
notifications.On("alert", handleAlert)
// Real-time data API
dataStream := client.Of("/stream")
dataStream.On("update", handleDataUpdate)
2. Access Level Separation¶
Use namespaces for different permission levels:
// Public namespace (everyone can access)
public := client.Of("/public")
public.On("announcement", handleAnnouncement)
// Authenticated users namespace
authenticated := client.Of("/authenticated")
authenticated.OnConnect(func() {
authenticated.Emit("auth", map[string]interface{}{
"token": userToken,
})
})
authenticated.On("private-data", handlePrivateData)
// Administrators namespace
admin := client.Of("/admin")
admin.OnConnect(func() {
admin.Emit("auth", map[string]interface{}{
"token": adminToken,
"role": "admin",
})
})
admin.On("admin-event", handleAdminEvent)
3. Virtual Rooms¶
Use namespaces as separate virtual rooms:
// Different chat rooms
lobby := client.Of("/chat/lobby")
lobby.On("message", handleLobbyMessage)
gameRoom := client.Of("/chat/game-room-1")
gameRoom.On("message", handleGameRoomMessage)
privateRoom := client.Of("/chat/private-123")
privateRoom.On("message", handlePrivateMessage)
4. Microservices¶
Each microservice can have its own namespace:
// User service
users := client.Of("/services/users")
users.On("user-update", handleUserUpdate)
// Payment service
payments := client.Of("/services/payments")
payments.On("payment-status", handlePaymentStatus)
// Notification service
notifications := client.Of("/services/notifications")
notifications.On("notify", handleNotification)
Architecture¶
Independent Lifecycle¶
Each namespace has its own lifecycle:
chat := client.Of("/chat")
api := client.Of("/api")
chat.OnConnect(func() {
fmt.Println("Chat connected")
chat.Emit("join", "general")
})
chat.OnDisconnect(func(reason string) {
fmt.Println("Chat disconnected:", reason)
})
api.OnConnect(func() {
fmt.Println("API connected")
api.Emit("subscribe", []string{"users", "posts"})
})
api.OnDisconnect(func(reason string) {
fmt.Println("API disconnected:", reason)
})
Isolated Events¶
Events in one namespace don't affect other namespaces:
chat := client.Of("/chat")
api := client.Of("/api")
// This handler only receives events from /chat
chat.On("message", func(data ...interface{}) {
fmt.Println("Chat message:", data[0])
})
// This handler only receives events from /api
api.On("message", func(data ...interface{}) {
fmt.Println("API message:", data[0])
})
// Emitting on /chat doesn't affect /api
chat.Emit("message", "Hello from chat")
api.Emit("message", "Hello from API")
Implementation Patterns¶
Pattern: Namespace Manager¶
Manage multiple namespaces centrally:
type NamespaceManager struct {
client *socketio.Socket
namespaces map[string]*socketio.Namespace
}
func NewNamespaceManager(url string) *NamespaceManager {
return &NamespaceManager{
client: socketio.New(url),
namespaces: make(map[string]*socketio.Namespace),
}
}
func (m *NamespaceManager) Get(path string) *socketio.Namespace {
if ns, exists := m.namespaces[path]; exists {
return ns
}
ns := m.client.Of(path)
m.namespaces[path] = ns
return ns
}
func (m *NamespaceManager) Close() error {
return m.client.Close()
}
// Usage
manager := NewNamespaceManager("ws://localhost:3000")
defer manager.Close()
chat := manager.Get("/chat")
chat.On("message", handleChatMessage)
api := manager.Get("/api")
api.On("data", handleAPIData)
Pattern: Namespace Wrapper¶
Wrap namespaces with additional logic:
type ChatNamespace struct {
ns *socketio.Namespace
username string
room string
}
func NewChatNamespace(client *socketio.Socket, username string) *ChatNamespace {
cn := &ChatNamespace{
ns: client.Of("/chat"),
username: username,
}
cn.setupHandlers()
return cn
}
func (cn *ChatNamespace) setupHandlers() {
cn.ns.OnConnect(func() {
fmt.Printf("%s connected to chat\n", cn.username)
})
cn.ns.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", cn.room, user, msg)
})
}
func (cn *ChatNamespace) JoinRoom(room string) {
cn.room = room
cn.ns.Emit("join-room", room)
}
func (cn *ChatNamespace) SendMessage(message string) {
cn.ns.Emit("message", cn.username, message)
}
func (cn *ChatNamespace) LeaveRoom() {
if cn.room != "" {
cn.ns.Emit("leave-room", cn.room)
cn.room = ""
}
}
// Usage
client := socketio.New("ws://localhost:3000")
chat := NewChatNamespace(client, "John")
chat.JoinRoom("general")
chat.SendMessage("Hello everyone!")
Pattern: Stateful Namespace¶
Maintain namespace-specific state:
type APINamespace struct {
ns *socketio.Namespace
subscriptions []string
cache map[string]interface{}
mu sync.RWMutex
}
func NewAPINamespace(client *socketio.Socket) *APINamespace {
api := &APINamespace{
ns: client.Of("/api"),
cache: make(map[string]interface{}),
}
api.setupHandlers()
return api
}
func (a *APINamespace) setupHandlers() {
a.ns.OnConnect(func() {
// Automatically resubscribe on reconnect
if len(a.subscriptions) > 0 {
a.ns.Emit("subscribe", a.subscriptions)
}
})
a.ns.On("update", func(data ...interface{}) {
key := data[0].(string)
value := data[1]
a.mu.Lock()
a.cache[key] = value
a.mu.Unlock()
fmt.Printf("Cache updated: %s\n", key)
})
}
func (a *APINamespace) Subscribe(topics []string) {
a.subscriptions = append(a.subscriptions, topics...)
a.ns.Emit("subscribe", topics)
}
func (a *APINamespace) Get(key string) (interface{}, bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value, exists := a.cache[key]
return value, exists
}
// Usage
client := socketio.New("ws://localhost:3000")
api := NewAPINamespace(client)
api.Subscribe([]string{"users", "posts", "comments"})
// Use cache
if value, ok := api.Get("users"); ok {
fmt.Println("Users:", value)
}
Pattern: Dynamic Namespaces¶
Create and destroy namespaces dynamically:
type DynamicNamespaceManager struct {
client *socketio.Socket
namespaces sync.Map // map[string]*socketio.Namespace
}
func NewDynamicNamespaceManager(url string) *DynamicNamespaceManager {
return &DynamicNamespaceManager{
client: socketio.New(url),
}
}
func (m *DynamicNamespaceManager) Join(path string) *socketio.Namespace {
if ns, loaded := m.namespaces.LoadOrStore(path, m.client.Of(path)); loaded {
return ns.(*socketio.Namespace)
}
ns := m.client.Of(path)
m.namespaces.Store(path, ns)
fmt.Printf("Namespace created: %s\n", path)
return ns
}
func (m *DynamicNamespaceManager) Leave(path string) {
if ns, ok := m.namespaces.LoadAndDelete(path); ok {
fmt.Printf("Namespace removed: %s\n", path)
// Note: There is no Close() method on Namespace,
// simply stop using the namespace
_ = ns
}
}
func (m *DynamicNamespaceManager) List() []string {
var paths []string
m.namespaces.Range(func(key, value interface{}) bool {
paths = append(paths, key.(string))
return true
})
return paths
}
// Usage
manager := NewDynamicNamespaceManager("ws://localhost:3000")
// User joins a room
roomNs := manager.Join("/rooms/room-123")
roomNs.On("message", handleRoomMessage)
// User leaves the room
manager.Leave("/rooms/room-123")
// View active rooms
fmt.Println("Active rooms:", manager.List())
Best Practices¶
1. Clear Organization¶
Use clear and consistent naming conventions:
// ✅ Good: clear hierarchical structure
client.Of("/chat/public")
client.Of("/chat/private")
client.Of("/api/v1")
client.Of("/api/v2")
client.Of("/admin/users")
client.Of("/admin/settings")
// ❌ Bad: inconsistent names
client.Of("/chat")
client.Of("/PRIVATE-CHAT")
client.Of("/api_v1")
client.Of("/AdminPanel")
2. Per-Namespace Connection Handling¶
Each namespace should handle its own connection:
chat := client.Of("/chat")
api := client.Of("/api")
// ✅ Good: each namespace handles its state
chat.OnConnect(func() {
chat.Emit("join", "general")
})
api.OnConnect(func() {
api.Emit("authenticate", token)
})
// ❌ Bad: assuming all connect at the same time
client.OnConnect(func() {
// This only executes for the default namespace
chat.Emit("join", "general") // May fail if /chat isn't connected
})
3. Avoid Namespace Explosion¶
Don't create too many namespaces unnecessarily:
// ✅ Good: namespaces by category
chat := client.Of("/chat")
chat.Emit("join-room", "general")
chat.Emit("join-room", "random")
// ❌ Bad: one namespace per room
general := client.Of("/chat/general")
random := client.Of("/chat/random")
tech := client.Of("/chat/tech")
// ... 100 more
4. Document Namespaces¶
Document what each namespace does:
// Namespace /chat: Real-time messaging
// Events: message, user-joined, user-left, typing
// Authentication: Required
chat := client.Of("/chat")
// Namespace /api: Real-time data
// Events: update, delete, create
// Authentication: JWT token
api := client.Of("/api")
// Namespace /admin: Administration panel
// Events: stats, user-action, system-alert
// Authentication: Admin token
admin := client.Of("/admin")
5. Per-Namespace Testing¶
Test each namespace independently:
func TestChatNamespace(t *testing.T) {
client := socketio.New("ws://localhost:3000")
defer client.Close()
chat := client.Of("/chat")
var received bool
chat.On("welcome", func(data ...interface{}) {
received = true
})
// Wait for connection
time.Sleep(100 * time.Millisecond)
if !received {
t.Error("Welcome event not received")
}
}
6. Namespace Cleanup¶
Clean up namespaces when you no longer need them:
type RoomManager struct {
client *socketio.Socket
rooms sync.Map
}
func (rm *RoomManager) JoinRoom(roomId string) {
ns := rm.client.Of("/rooms/" + roomId)
rm.rooms.Store(roomId, ns)
ns.OnConnect(func() {
fmt.Printf("Connected to room %s\n", roomId)
})
}
func (rm *RoomManager) LeaveRoom(roomId string) {
if ns, ok := rm.rooms.LoadAndDelete(roomId); ok {
// The namespace will stop receiving events
fmt.Printf("Leaving room %s\n", roomId)
_ = ns
}
}
func (rm *RoomManager) Cleanup() {
rm.rooms.Range(func(key, value interface{}) bool {
rm.rooms.Delete(key)
return true
})
}
Complete Example: Multi-Room Chat System¶
package main
import (
"fmt"
"sync"
socketio "github.com/arcaela/socket.io-client-go"
)
type ChatSystem struct {
client *socketio.Socket
user string
rooms map[string]*socketio.Namespace
roomsMutex sync.RWMutex
}
func NewChatSystem(url, username string) *ChatSystem {
return &ChatSystem{
client: socketio.New(url),
user: username,
rooms: make(map[string]*socketio.Namespace),
}
}
func (cs *ChatSystem) JoinRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
if _, exists := cs.rooms[roomName]; exists {
return fmt.Errorf("already in room %s", roomName)
}
// Create namespace for the room
room := cs.client.Of("/chat/" + roomName)
// Setup handlers
room.OnConnect(func() {
fmt.Printf("✅ Connected to room: %s\n", roomName)
room.Emit("join", cs.user)
})
room.OnDisconnect(func(reason string) {
fmt.Printf("❌ Disconnected from room %s: %s\n", roomName, reason)
})
room.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", roomName, user, msg)
})
room.On("user-joined", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👤 %s joined\n", roomName, user)
})
room.On("user-left", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👋 %s left\n", roomName, user)
})
cs.rooms[roomName] = room
return nil
}
func (cs *ChatSystem) LeaveRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
room, exists := cs.rooms[roomName]
if !exists {
return fmt.Errorf("not in room %s", roomName)
}
room.Emit("leave", cs.user)
delete(cs.rooms, roomName)
fmt.Printf("Left room: %s\n", roomName)
return nil
}
func (cs *ChatSystem) SendMessage(roomName, message string) error {
cs.roomsMutex.RLock()
room, exists := cs.rooms[roomName]
cs.roomsMutex.RUnlock()
if !exists {
return fmt.Errorf("not in room %s", roomName)
}
return room.Emit("message", cs.user, message)
}
func (cs *ChatSystem) ListRooms() []string {
cs.roomsMutex.RLock()
defer cs.roomsMutex.RUnlock()
rooms := make([]string, 0, len(cs.rooms))
for name := range cs.rooms {
rooms = append(rooms, name)
}
return rooms
}
func (cs *ChatSystem) Close() error {
return cs.client.Close()
}
func main() {
chat := NewChatSystem("ws://localhost:3000", "John")
defer chat.Close()
// Join multiple rooms
chat.JoinRoom("general")
chat.JoinRoom("tech")
chat.JoinRoom("random")
// Send messages
chat.SendMessage("general", "Hello everyone!")
chat.SendMessage("tech", "Anyone using Go?")
// View active rooms
fmt.Println("Active rooms:", chat.ListRooms())
// Leave a room
chat.LeaveRoom("random")
select {}
}