Live Dashboards
Build real-time dashboards that update instantly as data changes.
Overview
Real-time dashboards provide immediate visibility into your business metrics. With NoLag, you can push updates to dashboards the moment data changes, without polling or page refreshes.
App Setup
Rooms never exist implicitly, so create the app, its rooms, and its actors before any client connects. This guide uses one app with two rooms: live for metrics and ops for filtered booking updates. It also uses two actors: a service actor for the backend that publishes and a user actor for the dashboard that subscribes. Publishers never receive their own messages, so the two roles cannot share a token.
import { NoLagApi } from '@nolag/js-sdk'
const api = new NoLagApi(process.env.NOLAG_API_KEY) // nlg_live_...
// 1. Create the app and declare the topics it allows. The slug that comes back
// has a random suffix, so read it from the response.
const app = await api.apps.create({
name: 'Analytics',
slug: 'analytics',
topics: ['page-views', 'active-users', 'sales', 'bookings'],
})
console.log(app.slug) // e.g. "analytics-a3f9": this is your APP_SLUG
// 2. Create the rooms. Room slugs are kept exactly as you supply them.
await api.rooms.create(app.appId, { name: 'Live metrics', slug: 'live' })
await api.rooms.create(app.appId, { name: 'Operations', slug: 'ops' })
// 3. One actor per role. Each access token is shown once; keep both.
const publisher = await api.actors.create({ name: 'metrics-service', actorType: 'service' })
const dashboard = await api.actors.create({ name: 'dashboard', actorType: 'user' })
console.log(publisher.accessToken, dashboard.accessToken)Dashboard Setup
The dashboard connects with the user actor and subscribes to the metric topics. Your backend publishes { count, timestamp } to page-views and active-users, and { amount, timestamp } to sales, with the service actor.
import { NoLag } from '@nolag/js-sdk'
const APP_SLUG = 'analytics-a3f9' // the slug returned when you created the app
interface Metric {
count: number
timestamp: number
}
interface Sale {
amount: number
timestamp: number
}
const client = NoLag('dashboard_access_token')
client.on('error', (err) => console.error(err)) // unknown_topic etc. arrive here
await client.connect()
const dashboard = client.setApp(APP_SLUG).setRoom('live')
// Subscribe to real-time metrics
dashboard.subscribe('page-views')
dashboard.subscribe('active-users')
dashboard.subscribe('sales')
// Update charts in real-time. Payloads arrive as `unknown`; name the shape you expect.
dashboard.on<Metric>('page-views', (data) => {
pageViewsChart.update(data.count, data.timestamp)
})
dashboard.on<Metric>('active-users', (data) => {
activeUsersGauge.setValue(data.count)
})
dashboard.on<Sale>('sales', (data) => {
salesChart.addDataPoint(data.amount, data.timestamp)
totalSales.increment(data.amount)
})Filtering Dashboard Updates
Most dashboards show a subset of data. A few bookings, specific orders, or selected devices. Without filters, every update on the topic is delivered to every subscriber, even if they're only watching 3 items out of thousands.
Topic filters solve this at the infrastructure level. Subscribe with the IDs of the entities currently visible on screen, and only updates published with one of those filter values are delivered. When the user navigates, swap filters dynamically, no resubscribe needed. Filters are routing, not a privacy boundary: a subscriber with no filters is a wildcard subscriber and still receives every update on the topic.
import { NoLag } from '@nolag/js-sdk'
const APP_SLUG = 'analytics-a3f9' // the slug returned when you created the app
interface BookingUpdate {
status: string
guest?: string
reason?: string
}
const client = NoLag('dashboard_access_token')
await client.connect()
// The ops room carries booking updates
const ops = client.setApp(APP_SLUG).setRoom('ops')
// User is viewing 3 specific bookings on their dashboard
const visibleBookings = ['booking_42', 'booking_87', 'booking_153']
// Subscribe with filters: only receive updates for these bookings
ops.subscribe('bookings', {
filters: visibleBookings
})
ops.on<BookingUpdate>('bookings', (data, meta) => {
// meta.filter tells you which booking was updated
updateBookingCard(meta.filter, data)
})
// User navigates to a different page, swap filters instantly
function onPageChange(newBookingIds: string[]) {
ops.setFilters('bookings', newBookingIds)
}
// User opens a booking detail, add it to filters
function onBookingOpen(bookingId: string) {
ops.addFilters('bookings', [bookingId])
}
// User closes a booking detail, remove it
function onBookingClose(bookingId: string) {
ops.removeFilters('bookings', [bookingId])
}Publishing Filtered Updates
On the backend, publish with a filter to target specific entities. Only dashboards whose filter list includes that value receive the update, plus any subscriber that set no filters at all. The backend uses its own service actor:
import { NoLag } from '@nolag/js-sdk'
const APP_SLUG = 'analytics-a3f9' // the slug returned when you created the app
// Backend service: its own actor, so the dashboard actor receives what it publishes
const publisher = NoLag('metrics_service_access_token')
await publisher.connect()
const ops = publisher.setApp(APP_SLUG).setRoom('ops')
// Only dashboard users watching booking_42 receive this
ops.emit('bookings', {
status: 'confirmed',
guest: 'John Smith',
checkIn: '2026-03-15'
}, {
filter: 'booking_42' // the routing key
})
// Publish a different booking update
ops.emit('bookings', {
status: 'cancelled',
reason: 'Guest request'
}, {
filter: 'booking_87'
})Tip: Filtering happens at the infrastructure level, no payload inspection, no wasted bandwidth. Each filter value is its own routing key, up to 100 per topic, and values cannot contain /, #, +, or |. See Topic Filters for full details.
Use Cases
- Analytics dashboards - Page views, user sessions, conversion rates
- Sales dashboards - Revenue, orders, inventory levels
- Operations dashboards - Server health, error rates, response times
- Trading dashboards - Stock prices, market data, portfolio values
Architecture
- Backend services publish metrics to NoLag topics with their own
serviceactor - Dashboard clients subscribe to relevant topics with their own actors
- Charts and gauges update in real-time as data arrives
Best Practices
- Use filters to subscribe only to entities visible on screen to avoid receiving thousands of irrelevant updates
- Use
setFilterswhen the user navigates: the broker diffs the two lists, adds the new routing keys before removing the old ones, and never interrupts the filters that stay - Use separate topics for different metric types
- Batch updates for high-frequency data to reduce rendering overhead; the broker accepts 50 publishes per second per connection
- Use a hydration webhook to load the initial dashboard state on subscribe; its response arrives on the client's
hydrationevent - Consider data retention and historical data storage separately; NoLag delivers live updates and does not keep a history for you (see Replay for what it does keep)
Next Steps
- Topic Filters - full reference for filter-based subscriptions
- Topics & Pub/Sub
- Quality of Service