On this page
How to Set Up Basic Application Monitoring
Set up basic app monitoring with Prometheus metrics, a health endpoint, and Grafana dashboards. A tool-agnostic tutorial with a concrete example.
Quick answer
- Start with three things: a health endpoint, a few core metrics, and one dashboard.
- Use Prometheus to scrape metrics and Grafana to display them; both are free and open source.
- The success signal is a graph of request rate, error rate, and latency for your service.
What you’ll build
Minimal but real monitoring for a Node.js web app: a /health endpoint, Prometheus metrics scraped on a schedule, and a Grafana dashboard showing request rate, error rate, and latency. The approach is tool-agnostic; the same ideas apply to any stack.
Step 1 — Add a health endpoint
app.get("/health", (req, res) => {
res.json({ status: "ok", uptime: process.uptime() });
});How to verify it worked: curl localhost:3000/health returns {"status":"ok",...}. This endpoint is your cheapest alert source and your load balancer’s check.
Step 2 — Expose Prometheus metrics
Using the prom-client library:
const client = require("prom-client");
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics();
const httpRequests = new client.Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "status"],
});
app.use((req, res, next) => {
res.on("finish", () => {
httpRequests.inc({ method: req.method, status: res.statusCode });
});
next();
});
app.get("/metrics", async (req, res) => {
res.set("Content-Type", client.register.contentType);
res.end(await client.register.metrics());
});How to verify it worked: curl localhost:3000/metrics prints text lines like http_requests_total{method="GET",status="200"} 42.
Step 3 — Configure Prometheus to scrape
Create prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: "my-app"
static_configs:
- targets: ["localhost:3000"]Run Prometheus and open http://localhost:9090. In the query box, type rate(http_requests_total[5m]) and press Execute.
How to verify it worked: the query returns data points, proving Prometheus is scraping your app.
Step 4 — Build a dashboard in Grafana
Start Grafana, add Prometheus as a data source, and create three panels:
sum(rate(http_requests_total[5m]))— request rate.sum(rate(http_requests_total{status=~"5.."}[5m]))— error rate.histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))— p95 latency.
How to verify it worked: the panels update as you hit the app, and error rate rises when you trigger a failing route.
Where this bites vibecoders
AI assistants will happily paste a full observability stack — Prometheus, Grafana, Tempo, Loki — for an app with no metrics to scrape. Start with the endpoint and the counter, confirm real data flows, then grow. A monitoring stack with nothing to monitor is the most common false “I set up observability” feeling.
Where AI coding assistants get this wrong
- Installing dashboards before the app emits any metrics.
- Hardcoding dashboard JSON with panels that reference nonexistent metric names.
- Skipping the health endpoint, leaving no simple liveness signal.
- Emitting high-cardinality labels (like raw request IDs) that blow up storage.
Checklist
- Add a
/healthendpoint first. - Expose request rate, error rate, and latency as metrics.
- Confirm Prometheus scrapes real data before building dashboards.
- Keep metric labels low-cardinality (status codes, methods — not IDs).
- Alert on user-facing symptoms: error rate and latency, not just CPU.
FAQ
What is a scrape?
In Prometheus, a scrape is the scheduled HTTP fetch of a /metrics endpoint. Prometheus pulls metrics from your app at a fixed interval and stores them as a time series. The pull model means your app needs no agent — it just exposes an endpoint.
Why use Prometheus and Grafana together?
Prometheus collects and stores metrics and provides a query language. Grafana visualizes those metrics as dashboards and alerts. They are separate tools that pair well, but you can use either with alternatives.
What is the difference between a counter and a gauge?
A counter only increases (total requests), while a gauge can go up and down (current memory). For rates, you use the rate() function on counters, which is why request counts are counters, not gauges.