Skip to main content

Live Dashboard

A metrics dashboard that receives real-time updates from the server. The client subscribes to a channel and receives stat updates, chart data points, and alert notifications — all pushed from the server with no client publishing required.

Get the code for this example and run it yourself from the GitHub repository.

How it works​

This example uses the @hotsock/hotsock-js client library to connect a viewer to a channel that receives server-published metrics in real time. Below is a walkthrough of each piece.

Authentication​

The viewer needs a connect token with subscribe-only permission — no message publishing rights. This is a purely receive-only pattern:

{
"exp": 1693963905,
"scope": "connect",
"uid": "viewer",
"channels": {
"dashboard-abc123": {
"subscribe": true
}
}
}

Notice there's no messages claim at all. This connection can only receive messages published by the server, never send any.

Server-published messages​

Unlike the chat and to-do examples where clients publish messages, this dashboard receives data pushed from the server via the Publish Messages API. The server publishes three different event types to the same channel:

  • stats-update — Current metric values (active users, requests/sec, error rate, latency)
  • chart-data — A time-series data point for the chart
  • alert — System alerts with severity levels

Subscribing and binding multiple events​

After connecting, the client subscribes to the dashboard channel and binds separate handlers for each event type:

const channel = client.channels("dashboard-abc123")

channel.bind("hotsock.subscribed", () => {
setConnected(true)
})

channel.bind("stats-update", (message) => {
setPrevStats(stats)
setStats(message.data)
})

channel.bind("chart-data", (message) => {
setChartHistory((prev) => [...prev.slice(-19), message.data])
})

channel.bind("alert", (message) => {
setAlerts((prev) => [
{ ...message.data, timestamp: Date.now() },
...prev.slice(0, 4),
])
})

Each event type updates a different piece of state, allowing the UI to react independently to stats, chart data, and alerts.

Rendering real-time data​

Stat cards compare the current value to the previous value to show trend arrows. Conditional coloring highlights when metrics cross thresholds:

<StatCard
label="Error Rate"
value={`${(stats.errorRate * 100).toFixed(2)}%`}
trend={stats.errorRate < prevStats.errorRate ? "down" : "up"}
color={stats.errorRate > 0.05 ? "red" : "green"}
/>

The chart accumulates the last 20 data points and renders proportional bars using simple CSS:

{
chartHistory.map((point, i) => (
<div
key={i}
style={{ height: `${(point.requests / maxRequests) * 100}%` }}
className="bg-blue-500 rounded-t"
/>
))
}

Cleanup​

When the component unmounts, terminate the client to close the WebSocket connection:

useEffect(() => {
return () => {
client.terminate()
}
}, [])

Putting it all together​

The full flow is: the backend issues a subscribe-only token, the client connects and listens for multiple event types on a single channel, and the server pushes periodic updates. This pattern is ideal for dashboards, live feeds, status pages, or any scenario where data flows one-way from server to client.

For a deeper dive, see the Publish Messages API documentation and the Connect & Subscribe guide.