Building real-time dashboards has become essential for modern web applications. Deno Fresh, with its zero-client-side JavaScript approach and built-in TypeScript support, offers an excellent platform for creating efficient and responsive dashboards. Let's explore how to build one from scratch.
Setting Up Your Deno Fresh Project
First, ensure you have Deno installed on your system. Create a new Fresh project:
deno run -A -r https://fresh.deno.dev my-dashboard
cd my-dashboard
Creating the Dashboard Layout
Let's start with a clean, responsive layout for our dashboard:
// routes/index.tsx
import { Head } from "$fresh/runtime.ts";
export default function Dashboard() {
return (
<div class="p-4">
<Head>
<title>Real-time Dashboard</title>
</Head>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-white p-4 rounded shadow">
<h2>Active Users</h2>
<div id="activeUsers">0</div>
</div>
<div class="bg-white p-4 rounded shadow">
<h2>System Load</h2>
<div id="systemLoad">0%</div>
</div>
<div class="bg-white p-4 rounded shadow">
<h2>Memory Usage</h2>
<div id="memoryUsage">0 MB</div>
</div>
</div>
</div>
);
}
Implementing WebSocket Connection
Create a WebSocket handler to manage real-time updates:
// routes/api/ws.ts
import { HandlerContext } from "$fresh/server.ts";
export const handler = (_req: Request, ctx: HandlerContext): Response => {
if (_req.headers.get("upgrade") != "websocket") {
return new Response(null, { status: 501 });
}
const { socket, response } = Deno.upgradeWebSocket(_req);
socket.onopen = () => {
console.log("WebSocket connected!");
// Send periodic updates
setInterval(() => {
const data = {
activeUsers: Math.floor(Math.random() * 100),
systemLoad: Math.floor(Math.random() * 100),
memoryUsage: Math.floor(Math.random() * 1000),
};
socket.send(JSON.stringify(data));
}, 1000);
};
socket.onmessage = (e) => {
console.log("Received:", e.data);
};
socket.onerror = (e) => console.log("WebSocket error:", e);
socket.onclose = () => console.log("WebSocket closed");
return response;
};
Adding Real-time Updates
Let's implement the client-side code to handle WebSocket updates:
// static/js/dashboard.js
class DashboardUpdater {
constructor() {
this.connect();
}
connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
this.ws = new WebSocket(`${protocol}//${window.location.host}/api/ws`);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.updateDashboard(data);
};
this.ws.onclose = () => {
console.log('WebSocket closed. Reconnecting...');
setTimeout(() => this.connect(), 1000);
};
}
updateDashboard(data) {
document.getElementById('activeUsers').textContent = data.activeUsers;
document.getElementById('systemLoad').textContent = `${data.systemLoad}%`;
document.getElementById('memoryUsage').textContent = `${data.memoryUsage} MB`;
}
}
new DashboardUpdater();
Adding Data Visualization
Let's enhance our dashboard with some basic charts using Chart.js:
// Import in routes/index.tsx
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
// Add to static/js/dashboard.js
class DashboardCharts {
constructor() {
this.initializeCharts();
}
initializeCharts() {
const ctx = document.getElementById('systemLoadChart').getContext('2d');
this.systemLoadChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'System Load',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
}
updateChart(data) {
const timestamp = new Date().toLocaleTimeString();
this.systemLoadChart.data.labels.push(timestamp);
this.systemLoadChart.data.datasets[0].data.push(data.systemLoad);
if (this.systemLoadChart.data.labels.length > 10) {
this.systemLoadChart.data.labels.shift();
this.systemLoadChart.data.datasets[0].data.shift();
}
this.systemLoadChart.update();
}
}
Implementing Error Handling
Add robust error handling to ensure your dashboard remains reliable:
// Add to static/js/dashboard.js
class ErrorHandler {
static handleWebSocketError(error) {
console.error('WebSocket Error:', error);
// Display error message to user
const errorDiv = document.getElementById('errorMessages');
if (errorDiv) {
errorDiv.innerHTML = `Connection error: ${error.message}`;
errorDiv.style.display = 'block';
}
}
static clearErrors() {
const errorDiv = document.getElementById('errorMessages');
if (errorDiv) {
errorDiv.style.display = 'none';
errorDiv.innerHTML = '';
}
}
}
Performance Optimization
To ensure optimal performance, implement these best practices:
Throttle WebSocket updates:
function throttle(func, limit) { let inThrottle; return function(โฆargs) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } }
Implement data caching:
class DataCache { constructor(maxSize = 1000) { this.cache = new Map(); this.maxSize = maxSize; }
set(key, value) { if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, value); }
get(key) { return this.cache.get(key); } }
Testing Your Dashboard
Create a basic test suite to ensure reliability:
// tests/dashboard_test.ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("WebSocket connection test", async () => {
const ws = new WebSocket("ws://localhost:8000/api/ws");
await new Promise((resolve) => {
ws.onopen = () => {
assertEquals(ws.readyState, WebSocket.OPEN);
ws.close();
resolve();
};
});
});
Deployment Considerations
When deploying your real-time dashboard:
- Implement proper WebSocket connection pooling
- Set up monitoring for WebSocket connections
- Configure appropriate timeout settings
- Implement reconnection strategies
- Consider scaling strategies for multiple instances
Your real-time dashboard is now ready to handle live data updates efficiently. The combination of Deno Fresh's server-side rendering capabilities with WebSocket integration provides a powerful foundation for building responsive and scalable dashboards.
Remember to monitor your WebSocket connections and implement appropriate error handling and recovery mechanisms to ensure a robust production deployment.