Skip to content

Namespaces

Gerasimos (Makis) Maropoulos edited this page May 28, 2026 · 5 revisions

When to use this page

  • You need to scope events into independent groups (different feature areas, different protocol versions).
  • You're handling the connect/disconnect lifecycle and want to know when each event fires.
  • You want to disallow a client from connecting to a namespace.

What a namespace is

A namespace is a named collection of event handlers on a single underlying connection. One websocket connection (Conn) can be connected to many namespaces at once; each one is represented by an NSConn.

Think of namespaces like channels on a chat server, or independent API surfaces ("admin", "metrics", "chat") sharing a single transport. They are static: declared up front in the handler map on both server and client.

namespaces := neffos.Namespaces{
    "chat": neffos.Events{
        neffos.OnNamespaceConnect: func(c *neffos.NSConn, msg neffos.Message) error {
            // return non-nil to deny the connect
            return nil
        },
        "message": func(c *neffos.NSConn, msg neffos.Message) error {
            log.Printf("%s says: %s", c.Conn.ID(), string(msg.Body))
            return nil
        },
    },
    "admin": neffos.Events{
        "shutdown": func(c *neffos.NSConn, msg neffos.Message) error { /* ... */ return nil },
    },
}

A client can choose to connect to one, the other, or both. Clients that never call conn.Connect("admin", ...) never receive admin messages.

Built-in system events

These event names are fired by neffos itself. They start with _ to avoid colliding with your event names — use the neffos.OnNamespace* / neffos.OnRoom* constants instead of the literals.

Event Fires when Return non-nil error to...
OnNamespaceConnect Before the namespace handshake completes. ...deny the connection. The remote side gets the error text.
OnNamespaceConnected After the namespace handshake succeeded. ...the return is ignored; the connection is already up.
OnNamespaceDisconnect When either side disconnects from the namespace. ...for server-side, refuse the disconnect (client retries). For client-side, the return is ignored.
OnRoomJoin Before joining a room. ...deny the join.
OnRoomJoined After the join succeeded. (Ignored.)
OnRoomLeave Before leaving a room. ...deny the leave.
OnRoomLeft After the leave succeeded. (Ignored.)
OnAnyEvent Wildcard: any incoming event with no matching handler. ...send the error back.
OnNativeMessage Raw WebSocket frames (only in the empty namespace). ...send the error back.

Connecting (client side)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

ns, err := conn.Connect(ctx, "chat")
if err != nil {
    log.Fatal(err)
}
ns.Emit("message", []byte("hello"))

Connect blocks until the namespace handshake completes or ctx fires. The first parameter MUST have a deadline in production — otherwise a server that refuses to ack will pin this goroutine.

Looking up a connected namespace

ns := c.Namespace("chat") // nil if not connected

Conn.Namespace(name) is cheap (one map lookup under a sync.RWMutex.RLock).

Disconnecting

err := ns.Disconnect(ctx)

Sends OnNamespaceDisconnect to the other side and fires the local event. Fail-safe: if the connection is dead, Disconnect still cleans up the local state.

Disconnect from every namespace at once:

err := c.DisconnectAll(ctx)

DisconnectAll walks the connected namespaces under the lock; the remote side is notified for each.

When to use multiple namespaces

Use namespaces when:

  • Authorization differs. Admin events vs. user events on the same socket.
  • Versioning. v1 and v2 namespaces during a rolling upgrade.
  • Independent feature surfaces. Chat and notifications, where you don't want a noisy chat to drown out time-sensitive notifications (they're queued per-namespace).

Use a single empty namespace ("") when your app has one event surface — it's simpler and most public examples use this pattern.

Server-side gating in OnNamespaceConnect

"chat": neffos.Events{
    neffos.OnNamespaceConnect: func(c *neffos.NSConn, msg neffos.Message) error {
        user, _ := c.Conn.Get("user").(*User)
        if user == nil {
            return errors.New("not authenticated")
        }
        if !user.CanChat() {
            return errors.New("chat is disabled for your account")
        }
        return nil
    },
}

The error text is sent to the client and surfaces as msg.Err (or a rejected Connect promise on the JS side). Register the error via neffos.RegisterKnownError if you want equality matching across the wire — see Errors.

Diagram: nesting

flowchart TB
    S[Server]
    S --> C1[Conn user-1]
    S --> C2[Conn user-2]
    C1 --> NS1A[NSConn chat]
    C1 --> NS1B[NSConn admin]
    C2 --> NS2A[NSConn chat]
    NS1A --> R1[Room lobby]
    NS1A --> R2[Room support]
    NS2A --> R1
Loading

A Conn can have many NSConns; an NSConn can be in many Rooms. Rooms are scoped to their namespace — lobby in chat is unrelated to lobby in admin.

See also

  • Rooms — joining and broadcasting inside a namespace
  • The ask method — request/response inside an event handler
  • Authentication — gating namespace connects on auth
  • Errors — meaningful errors from OnNamespaceConnect

Clone this wiki locally