Skip to content

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 {}
}

See Also