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 chartalert— 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.