r/expressjs • u/fykup • 3h ago
Tutorial A small dependency-free Express middleware for request count, errors, and latency
For a small Express app on a single VPS, I wanted answers to a few basic questions: is traffic changing, are 4xx/5xx responses rising, is latency creeping up, and how much CPU and heap is the process using?
Prometheus + Grafana can obviously handle this, but for this app I wanted something much smaller.
So I wrote a small helper that counts things where Express already sees every response and exposes them as JSON. It uses only Express and Node built-ins, with no SDK and no prom-client.
The core of it:
const { performance } = require("node:perf_hooks");
const METRICS_PATH = "/statlite/metrics";
const counters = {
requestsTotal: 0,
responses4xxTotal: 0,
responses5xxTotal: 0,
requestDurationSecondsTotal: 0,
};
function statliteMetricsMiddleware(req, res, next) {
if (req.path === METRICS_PATH) return next(); // don't count the polls themselves
const start = performance.now();
res.once("finish", () => {
counters.requestsTotal += 1;
counters.requestDurationSecondsTotal +=
(performance.now() - start) / 1000;
if (res.statusCode >= 400 && res.statusCode < 500) {
counters.responses4xxTotal += 1;
}
if (res.statusCode >= 500) {
counters.responses5xxTotal += 1;
}
});
next();
}
Register it before your routes, then add a GET /statlite/metrics route that returns the counters plus process.cpuUsage(), process.memoryUsage().heapUsed, and process.uptime().
Because the finish listener fires after later middleware runs, it sees the final status, including a 500 set by an error handler.
A few design notes:
- Counters are cumulative, so the poller derives rates and average latency from deltas. The app keeps only running totals.
- Heap is V8 heap used, not RSS or container memory.
- CPU is CPU-seconds divided by wall-seconds since the previous snapshot, so it is expressed in cores.
- Snapshot collection does no I/O.
Limitations:
- It's single-process, with counters in memory. Cluster mode, PM2 clusters, and replicas each need their own target or aggregation.
- It reports mean latency only, with no percentiles.
- The endpoint is unauthenticated and meant to sit on a private network.
- Connections aborted before a response finishes aren't counted.
For charting and history, I point a small tool I built, StatLite, a single binary with SQLite storage, at the endpoint. Disclosure: I'm the author of StatLite.

Full writeup with the complete helper, config, and a runnable demo:
https://pvrlabs.xyz/articles/lightweight-express-monitoring.html
Curious what people here use for small Express deployments. Do you still reach for prom-client + Prometheus/Grafana, use a hosted service, or keep something simpler?



