hono-status-monitor
v1.0.7
Published
Real-time server monitoring dashboard for Hono.js. Works with Node.js and Cloudflare Workers. Express-status-monitor style metrics with polling updates.
Maintainers
Readme
hono-status-monitor
Real-time server monitoring dashboard for Hono.js applications. Works with Node.js and Cloudflare Workers.
✨ Features
| Feature | Description | |---------|-------------| | 7 Real-Time Metrics | CPU, Memory, Heap, Load Average, Response Time, RPS, Event Loop Lag | | Response Percentiles | P50, P95, P99 latency tracking for accurate performance insights | | Route Analytics | Top routes by traffic, slowest routes, routes with most errors | | Error Tracking | Recent errors with timestamps, paths, and status codes | | Visual Alerts | Automatic warnings when CPU >80%, Memory >90%, Response >500ms | | Dark Mode | Toggle with localStorage persistence | | Polling Updates | Configurable polling interval (1s default for Node.js, 5s for edge) | | Edge Support | Works in Cloudflare Workers and edge runtimes | | Configurable | Custom thresholds, paths, titles, polling intervals, and more |
📦 Installation
npm install hono-status-monitor
# or
yarn add hono-status-monitor
# or
pnpm add hono-status-monitor🚀 Quick Start
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { statusMonitor } from 'hono-status-monitor';
const app = new Hono();
// Create status monitor
const monitor = statusMonitor();
// Add middleware to track ALL requests (must be first!)
app.use('*', monitor.middleware);
// Mount status dashboard at /status
app.route('/status', monitor.routes);
// Your routes
app.get('/', (c) => c.text('Hello World!'));
// Start server
const server = serve({ fetch: app.fetch, port: 3000 });
// Optional: initSocket is now a no-op, can be removed
// monitor.initSocket(server);
console.log('📊 Status monitor: http://localhost:3000/status');⚙️ Configuration
const monitor = statusMonitor({
// Dashboard path (default: '/status')
path: '/status',
// Dashboard title (default: 'Server Status')
title: 'My App Status',
// Dashboard polling interval in ms (default: 1000 for Node.js, 5000 for edge)
pollingInterval: 1000,
// Metrics collection interval in ms (default: 1000)
updateInterval: 1000,
// History retention in seconds (default: 60)
retentionSeconds: 60,
// Max recent errors to store (default: 10)
maxRecentErrors: 10,
// Max routes to show in analytics (default: 10)
maxRoutes: 10,
// Alert thresholds
alerts: {
cpu: 80, // CPU percentage
memory: 90, // Memory percentage
responseTime: 500, // Response time in ms
errorRate: 5, // Error rate percentage
eventLoopLag: 100 // Event loop lag in ms
},
// Custom database health check (optional)
healthCheck: async () => {
const start = performance.now();
await mongoose.connection.db?.admin().ping();
return {
connected: mongoose.connection.readyState === 1,
latencyMs: performance.now() - start,
name: 'MongoDB'
};
},
// Custom path normalization (optional)
normalizePath: (path) => {
return path
.replace(/\/users\/\d+/g, '/users/:id')
.replace(/\/posts\/[a-f0-9]{24}/g, '/posts/:id');
},
// Enable cluster mode for PM2 (auto-detected by default)
clusterMode: true
});📊 Dashboard Sections
Header
- Server hostname
- Connection status (Live/Offline)
- Dark mode toggle
Stats Bar
- Uptime
- Total requests
- Active connections
- Error rate
Response Percentiles
- Average response time
- P50 (median)
- P95
- P99
Real-Time Charts
- CPU usage
- Memory usage (MB)
- Heap usage (MB)
- Load average
- Response time (ms)
- Requests per second
- Event loop lag (ms)
Route Analytics
- 🔥 Top Routes (by request count)
- 🐢 Slowest Routes (by avg response time)
HTTP Status Codes
- 2xx success count
- 3xx redirect count
- 4xx client error count
- 5xx server error count
- Rate limited count
Recent Errors
- Last 10 errors with timestamp, path, and status code
Health Checks
- Database connection status and latency
- Heap total and growth rate
Process Info
- Node.js version
- Platform
- PID
- CPU count
🔌 API Endpoints
| Endpoint | Description |
|----------|-------------|
| GET /status | Dashboard HTML page |
| GET /status/api/metrics | JSON API with full metrics snapshot |
JSON API Response
{
"snapshot": {
"timestamp": 1704067200000,
"cpu": 12.5,
"memoryMB": 256.4,
"memoryPercent": 15.8,
"heapUsedMB": 48.2,
"heapTotalMB": 64.0,
"loadAvg": 1.25,
"uptime": 86400,
"processUptime": 3600,
"responseTime": 15.4,
"rps": 125,
"totalRequests": 450000,
"activeConnections": 42,
"eventLoopLag": 2.1,
"percentiles": {
"avg": 15.4,
"p50": 12.0,
"p95": 45.0,
"p99": 120.0
},
"topRoutes": [...],
"slowestRoutes": [...],
"recentErrors": [...],
"alerts": {
"cpu": false,
"memory": false,
"responseTime": false,
"errorRate": false,
"eventLoopLag": false
},
"database": {
"connected": true,
"latencyMs": 1.2
}
},
"charts": {
"cpu": [{ "timestamp": 1704067200000, "value": 12.5 }, ...],
"memory": [...],
"heap": [...],
"loadAvg": [...],
"responseTime": [...],
"rps": [...],
"eventLoopLag": [...],
"errorRate": [...]
}
}🎛️ Advanced Usage
Rate Limit Tracking
// In your rate limiter middleware
import { monitor } from './your-app';
if (isRateLimited) {
monitor.trackRateLimit(true); // Track blocked request
return c.text('Too many requests', 429);
}
monitor.trackRateLimit(false); // Track allowed requestCustom Path Normalization
Group similar routes for meaningful analytics:
const monitor = statusMonitor({
normalizePath: (path) => {
return path
// Replace user IDs
.replace(/\/users\/\d+/g, '/users/:id')
// Replace MongoDB ObjectIds
.replace(/\/[a-f0-9]{24}/g, '/:id')
// Replace UUIDs
.replace(/\/[0-9a-f-]{36}/g, '/:uuid');
}
});Multiple Health Checks
const monitor = statusMonitor({
healthCheck: async () => {
const dbStart = performance.now();
const dbConnected = mongoose.connection.readyState === 1;
if (dbConnected) {
await mongoose.connection.db?.admin().ping();
}
return {
connected: dbConnected,
latencyMs: performance.now() - dbStart,
name: 'MongoDB'
};
}
});PM2 / Cluster Mode Support
To enable metrics aggregation across multiple processes (Cluster Mode), you need a specific entry point script that handles message relaying between workers.
- Create a
cluster.ts(or.js) entry file:
import cluster from 'node:cluster';
import * as os from 'node:os';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork();
// Relay messages between workers
worker.on('message', (message) => {
if (message?.type === 'worker-metrics') {
// Broadcast to all workers (including sender)
for (const id in cluster.workers) {
cluster.workers[id]?.send(message);
}
}
});
}
cluster.on('exit', (worker) => {
console.log(`worker ${worker.process.pid} died`);
// Replace dead worker
const newWorker = cluster.fork();
newWorker.on('message', (message) => {
if (message?.type === 'worker-metrics') {
for (const id in cluster.workers) {
cluster.workers[id]?.send(message);
}
}
});
});
} else {
// Workers share the TCP connection in this file
await import('./index'); // Path to your main app file
}- Run this cluster script with PM2:
# Start the cluster script as a SINGLE PM2 instance
# (The script itself manages the worker processes)
pm2 start cluster.ts --name my-appNote: Do NOT use pm2 start app.ts -i max directly, as PM2's default isolation prevents workers from sharing metrics without an external adapter (like Redis). Using the script above provides a zero-dependency aggregation solution.
🛡️ Security Considerations
The status dashboard exposes server metrics. Consider:
- Authentication: Add middleware to protect the
/statusroute - Rate Limiting: Limit access to the dashboard
- Internal Only: Only expose on internal networks
import { basicAuth } from 'hono/basic-auth';
// Protect status routes
app.use('/status/*', basicAuth({
username: 'admin',
password: process.env.STATUS_PASSWORD!
}));
app.route('/status', monitor.routes);☁️ Cloudflare Workers / Edge Support
This package provides a separate edge entry point that has zero Node.js dependencies.
Available Metrics in Edge Environments
| Metric | Available | Notes | |--------|-----------|-------| | Request count & RPS | ✅ | Fully supported | | Response time percentiles | ✅ | P50, P95, P99, Avg | | Status code breakdown | ✅ | 2xx, 3xx, 4xx, 5xx | | Route analytics | ✅ | Top routes, slowest routes | | Error tracking | ✅ | Recent errors with timestamps | | CPU / Memory / Heap | ❌ | Not available in Workers | | Event loop lag | ❌ | Not available in Workers | | Load average | ❌ | Not available in Workers | | WebSocket real-time | ❌ | Uses polling (configurable, default 5s) |
Usage in Cloudflare Workers
Important: Use the
/edgeimport path for Cloudflare Workers!
import { Hono } from 'hono';
// Use the edge-specific import (no Node.js dependencies)
import { statusMonitor } from 'hono-status-monitor/edge';
const app = new Hono();
// Create status monitor with custom polling interval
const monitor = statusMonitor({
pollingInterval: 3000 // Update dashboard every 3 seconds (default: 5000)
});
// Add middleware to track requests
app.use('*', monitor.middleware);
// Mount status dashboard
app.route('/status', monitor.routes);
// Your routes
app.get('/', (c) => c.text('Hello from Cloudflare Workers!'));
export default app;Note: In edge environments, the dashboard uses HTTP polling instead of WebSocket for updates. The polling interval is configurable via
pollingInterval(defaults to 5 seconds for edge, 1 second for Node.js).
Force Edge Mode in Node.js
You can also use the edge-compatible monitor in Node.js if you don't need system metrics:
import { statusMonitor } from 'hono-status-monitor/edge';
const monitor = statusMonitor();📋 Requirements
- Node.js >= 18.0.0 (for Node.js mode)
- Hono.js >= 4.0.0
- @hono/node-server >= 1.0.0 (for Node.js mode only)
🤝 Contributing
Contributions welcome! Please read the contributing guidelines first.
📄 License
MIT © Vinit Kumar Goel
Made with ❤️ for the Hono.js community
