Topics & Pub/Sub

Topics are the foundation of NoLag's messaging system. Learn how to use topics for efficient pub/sub communication.

What are Topics?

Topics are named channels that clients can subscribe to and publish messages on. When a message is published to a topic, every other subscribed client receives it in real-time. Publishers never receive their own messages, whatever options they pass, so append your own message to the UI locally when you send it.

Topics live inside rooms, and rooms inside apps. The fluent client.setApp(slug).setRoom(slug) context below adds the app/room/ prefix for you. Pass the app slug exactly as the control plane returned it when you created the app: slugs get a random 4-hex suffix, so my-app is stored as something like my-app-a3f9.

import { NoLag } from '@nolag/js-sdk'

const client = NoLag('your_access_token')
await client.connect()

const room = client.setApp('my-app').setRoom('general')

// Subscribe to a topic
room.subscribe('notifications')

// Listen for messages (data + meta)
room.on('notifications', (data, meta) => {
  console.log('Received:', data)
  console.log('Is replay:', meta.isReplay)
  console.log('Message ID:', meta.msgId)
  if (meta.filter) console.log('Filter:', meta.filter)
})

// Publish a message
room.emit('notifications', {
  type: 'alert',
  message: 'New notification!'
})

Topic Naming

A topic name is a single token. The control plane validates it against ^[a-zA-Z0-9_:-]+$, so letters, digits, _, : and - are allowed and / is not. Topics are declared on the app and inherited by every room in it.

  • messages - chat messages
  • _typing - typing indicators
  • order:status - order status updates
  • sensor-readings - device telemetry

The only hierarchy in NoLag is app/room/topic. There are no nested topic paths: what you would express as users/123/notifications elsewhere is a room user-123 with a notifications topic.

Naming Best Practices

  • Use lowercase letters and hyphens
  • Name the kind of data, not the entity: put the entity in the room name, or in a filter
  • Keep names stable; they are part of the app configuration, not something created on the fly
  • Avoid special characters other than _, : and -

Addressing a Topic

The room context is a convenience. The client itself works on full paths, and the two are interchangeable:

// Room context adds the app/room prefix
const room = client.setApp('my-app-a3f9').setRoom('general')
room.subscribe('messages')
room.on('messages', (data) => console.log(data))

// Equivalent, addressing the full path directly
client.subscribe('my-app-a3f9/general/messages')
client.on('my-app-a3f9/general/messages', (data) => console.log(data))

Every path must name a room that exists. Rooms are created through the control plane (or POST /apps/{appId}/rooms/ensure when the app has config.autoProvisionRooms=true), never implicitly by the broker. A subscribe or publish to a room the broker does not know is answered with unknown_topic (42940) on the error event, and nothing is delivered.

There are no client-side wildcards. A subscription matches exactly one app/room/topic; to receive a subset of a topic's traffic, use filters.

Message Structure

Messages can contain any JSON-serializable data:

// Publish structured message
room.emit('chat', {
  type: 'chat_message',
  sender: {
    id: 'user-123',
    name: 'Alice'
  },
  content: 'Hello, World!',
  timestamp: Date.now(),
  metadata: {
    room: 'general',
    thread: null
  }
})

Message Events

Subscribe to various events on a topic. Message handlers receive two arguments: the message data and a meta object containing:

  • isReplay - true only for messages delivered by a replay, which happens for load-balanced worker groups only; an ordinary reconnect replays nothing
  • msgId - Unique message ID (for acknowledgement)
  • filter - The filter value the message was published with (if any)
import { NoLagServerError } from '@nolag/js-sdk'

// Subscribe and handle events
room.subscribe('chat')

// Incoming messages (includes meta with isReplay, msgId, filter)
room.on('chat', (data, meta) => {
  console.log('Data:', data, 'Meta:', meta)
})

// Connection events
client.on('connect', () => {
  console.log('Successfully connected')
})

client.on('error', (err) => {
  if (err instanceof NoLagServerError) {
    // Broker-side refusal: unknown room, no permission, and so on
    console.error(err.code, err.error, err.topic, err.hint)
  } else {
    console.error('Connection error:', err)
  }
})

Unsubscribing

Unsubscribe when you no longer need to receive messages:

// Unsubscribe from a topic
room.unsubscribe('chat')

// Disconnect client (synchronous, no await needed)
client.disconnect()

Next Steps