Build a Real‑Time Dashboard with Deno, Materialize, and Chart.js
This tutorial walks through creating a live dashboard by generating simulated user‑score events, storing them in Redpanda, materializing the data with Materialize, streaming updates via a Deno WebSocket backend, and visualizing the results in real time with Chart.js on the frontend.
This tutorial demonstrates how to build a real‑time dashboard using Deno, Materialize, WebSockets, and Chart.js.
Overview
The project consists of a simulated service that continuously generates user‑score events, stores them in a Redpanda topic, materializes the data with Materialize, and streams the latest scores to a frontend chart via a Deno WebSocket server.
Prerequisites
Docker
Docker Compose
Running the Demo
Clone the repository and start the containers:
git clone https://github.com/bobbyiliev/materialize-tutorials.git cd materialize-tutorials/mz-deno-live-dashboard docker-compose build docker-compose up -dAfter the containers are up, open http://localhost in a browser to view the live chart.
Implementation Setup
Materialize DDL executed by the Deno service:
CREATE SOURCE score FROM KAFKA BROKER 'redpanda:9092' TOPIC 'score_topic' FORMAT BYTES; CREATE VIEW score_view AS
SELECT * FROM (
SELECT (data->>'user_id')::int AS user_id,
(data->>'score')::int AS score,
(data->>'created_at')::double AS created_at
FROM (SELECT CAST(data AS jsonb) AS data FROM (SELECT convert_from(data, 'utf8') AS data FROM score))
); CREATE MATERIALIZED VIEW score_view_mz AS
SELECT (SUM(score))::int AS user_score, user_id
FROM score_view GROUP BY user_id;To verify the views and source, run the Materialize CLI:
docker-compose run mzcli psql -U materialize -h localhost -p 6875 materialize SHOW VIEWS; SHOW sources;Use TAIL to stream updates:
COPY ( TAIL score_view_mz ) TO STDOUT; COPY ( TAIL score_view_mz WITH (SNAPSHOT = false) ) TO STDOUT;Deno Backend Code
import { WebSocketClient, WebSocketServer } from "https://deno.land/x/[email protected]/mod.ts";
import { Client } from "https://deno.land/x/postgres/mod.ts";
const client = new Client({
user: "materialize",
database: "materialize",
hostname: "materialized",
port: 6875,
});
await client.connect();
await client.queryObject('BEGIN');
await client.queryObject(`DECLARE c CURSOR FOR TAIL score_view_mz WITH (SNAPSHOT = false)`);
const wss = new WebSocketServer(8080);
wss.on("connection", async function (ws: WebSocketClient) {
console.log("Client connected");
setInterval(async () => {
const result = await client.queryObject<{ mz_timestamp: string; mz_diff: number, user_id: number, user_score: number}>(`FETCH ALL c`);
for (const row of result.rows) {
let message = { user_id: row.user_id, user_score: row.user_score };
broadcastEvent(message);
}
}, 1000);
});
const broadcastEvent = (message: any) => {
wss.clients.forEach((ws: WebSocketClient) => {
ws.send(JSON.stringify(message));
});
};Frontend Setup
The frontend uses plain HTML and Chart.js (no framework). It connects to the Deno WebSocket server and updates the chart in real time.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="w-full mt-10">
<canvas id="myChart"></canvas>
</div>
<script>
const ctx = document.getElementById("myChart");
const myChart = new Chart(ctx, {
type: "bar",
data: {
labels: [ "Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6" ],
datasets: [{
label: "# of points",
data: [0, 0, 0, 0, 0, 0],
backgroundColor: [
"rgba(255, 99, 132, 0.2)",
"rgba(54, 162, 235, 0.2)",
"rgba(255, 206, 86, 0.2)",
"rgba(75, 192, 192, 0.2)",
"rgba(153, 102, 255, 0.2)",
"rgba(255, 159, 64, 0.2)"
],
borderColor: [
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)"
],
borderWidth: 1
}]
},
options: { scales: { y: { beginAtZero: true } } }
});
const webSocket = new WebSocket("ws://127.0.0.1:8080");
webSocket.onmessage = function (message) {
const dataObj = JSON.parse(message.data);
const index = dataObj.user_id - 1;
myChart.data.datasets[0].data[index] = dataObj.user_score;
myChart.update();
};
</script>
</body>
</html>Conclusion
By keeping the Deno application running, it continuously subscribes to Materialize, fetches the latest scores via a TAIL cursor, and pushes updates to the frontend chart, resulting in a fully functional real‑time dashboard.
Author: 华山
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
