0Pricing
Node.js Backend Development Bootcamp · Lesson

Exposing Application Metrics and the RED Method

Publish rate, errors, and duration metrics in Prometheus format and define meaningful SLIs.

Exposing Application Metrics and the RED Method is a free Node.js Backend Development Bootcamp lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Node.js Backend Development Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Metrics Matter

Logs tell you what happened in a single request. Metrics tell you how your whole service is behaving right now and over time. They are cheap to store, aggregate well, and power dashboards and alerts.

A metric is a numeric measurement sampled over time. In a Node.js backend you typically export metrics in Prometheus text format over an HTTP endpoint (conventionally /metrics). Prometheus scrapes that endpoint every few seconds and stores the time series.

  • Counter — only goes up (total requests, total errors).
  • Gauge — goes up and down (active connections, queue depth).
  • Histogram — buckets observations (request durations) to compute quantiles.

The Prometheus Text Format

Before wiring up libraries, it helps to see what the /metrics endpoint actually returns. Each line is metric_name{label="value"} number. The # HELP and # TYPE comment lines describe each metric.

Labels let one metric name carry many dimensions (per route, per status code). Below is a tiny generator that builds this exposition format by hand so you can see exactly what Prometheus parses.

function renderMetrics(samples) {
  const lines = [
    '# HELP http_requests_total Total HTTP requests',
    '# TYPE http_requests_total counter',
  ];
  for (const s of samples) {
    const labels = Object.entries(s.labels)
      .map(([k, v]) => k + '="' + v + '"')
      .join(',');
    lines.push('http_requests_total{' + labels + '} ' + s.value);
  }
  return lines.join('\n');
}

const out = renderMetrics([
  { labels: { method: 'GET', route: '/users', status: '200' }, value: 1024 },
  { labels: { method: 'GET', route: '/users', status: '500' }, value: 7 },
]);
console.log(out);

Introducing the RED Method

The RED method is a focused recipe for monitoring request-driven services. For every service you track three signals:

  • R — Rate: requests per second the service is handling.
  • E — Errors: rate of failed requests (typically HTTP 5xx, sometimes 4xx).
  • D — Duration: distribution of how long requests take (latency).

RED is the request-side complement to USE (Utilization, Saturation, Errors) which targets resources like CPU and disk. For an HTTP API, RED maps almost perfectly onto user-facing experience: throughput, failure rate, and latency.

The elegant part: all three derive from instrumenting just one thing — the request lifecycle.

Installing prom-client

In Node.js the de-facto library is prom-client. It manages a registry of metrics and renders them in Prometheus format. It also ships default metrics (event loop lag, heap usage, GC) that you enable with one call.

Create a single shared registry so every part of your app registers into the same place.

const client = require('prom-client');

const register = new client.Registry();
register.setDefaultLabels({ app: 'orders-api' });

// Node.js runtime metrics: event loop lag, heap, GC, fd count...
client.collectDefaultMetrics({ register });

module.exports = { client, register };

R — A Counter for Request Rate

Rate is not stored directly. You store a monotonic counter of total requests, and Prometheus computes the per-second rate at query time with rate(http_requests_total[1m]).

Give the counter labels for method, route, and status_code. Crucially, use the route template (/users/:id) and not the raw URL (/users/42) — otherwise every id becomes a new time series and your cardinality explodes.

const client = require('prom-client');

const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
});

// On each finished request:
httpRequestsTotal.inc({
  method: 'GET',
  route: '/users/:id',   // template, NOT /users/42
  status_code: '200',
});

D — A Histogram for Duration

Duration needs a histogram so you can compute percentiles (p50, p95, p99). A histogram counts observations into predefined buckets (upper bounds in seconds). Choose buckets that span your expected latency range.

A histogram automatically exposes three series per label set: _bucket, _sum, and _count. From _sum and _count you also get the average; from _bucket you estimate quantiles with histogram_quantile().

const client = require('prom-client');

const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'route', 'status_code'],
  // Tuned for a typical web API (5ms .. 5s)
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});

// Observe a completed request that took 0.137s
httpRequestDuration.observe(
  { method: 'GET', route: '/users/:id', status_code: '200' },
  0.137
);

One Middleware to Capture R, E, and D

The whole point of RED is that a single piece of instrumentation feeds all three signals. An Express-style middleware starts a timer, lets the request run, then on res.finish records the count and duration with the status code as a label.

Errors need no separate metric — they are just the counter filtered by status_code=~"5.." at query time. This is framework code (Express), so treat it as a reference pattern rather than a standalone program.

function metricsMiddleware(req, res, next) {
  const end = httpRequestDuration.startTimer({ method: req.method });

  res.on('finish', () => {
    // req.route?.path gives the template, e.g. '/users/:id'
    const route = (req.route && req.route.path) || req.path;
    const labels = {
      method: req.method,
      route,
      status_code: String(res.statusCode),
    };
    httpRequestsTotal.inc(labels);
    end(labels); // stops timer and records duration with these labels
  });

  next();
}

app.use(metricsMiddleware);

Exposing the /metrics Endpoint

Prometheus pulls metrics, so you must expose them over HTTP. Add a GET /metrics route that returns register.metrics() with the correct content type. This is the endpoint you list as a scrape target in prometheus.yml.

Keep this endpoint internal (bind to a private interface or protect it) — it can leak operational detail and is a tempting DoS target.

const { register } = require('./metrics');

app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// prometheus.yml scrape config:
// scrape_configs:
//   - job_name: 'orders-api'
//     scrape_interval: 15s
//     static_configs:
//       - targets: ['orders-api:3000']

Querying RED with PromQL

Once data is flowing, you express each RED signal as a PromQL query for dashboards and alerts:

  • Rate: sum(rate(http_requests_total[5m])) by (route)
  • Errors (ratio): sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
  • Duration (p95): histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

Note the error ratio is a fraction of total requests, not a raw count. An error rate of 0.02 means 2% of requests failed — far more meaningful than "40 errors" with no denominator.

From Metrics to SLIs and SLOs

A Service Level Indicator (SLI) is a precisely defined measure of service health, usually expressed as good events / valid events. RED metrics are exactly the raw material for SLIs:

  • Availability SLI: non-5xx responses / total responses.
  • Latency SLI: requests faster than 300ms / total requests.

A Service Level Objective (SLO) is a target for an SLI over a window, e.g. "99.9% of requests succeed over 30 days." The complement of the SLO is your error budget (0.1%), which tells you how much failure you can tolerate before you must stop shipping features and fix reliability.

Computing a Latency SLI From Buckets

A latency SLI like "fraction of requests under 300ms" comes straight from histogram bucket counts: take the cumulative count in the le="0.3" bucket and divide by the total count. Here is a standalone simulation of that computation over two scrape samples.

// Cumulative bucket counts from a histogram, le = upper bound (seconds)
const sample = {
  total: 10000,
  buckets: {
    '0.1': 6000,
    '0.3': 9700,  // 9700 requests finished within 300ms
    '1.0': 9990,
    '+Inf': 10000,
  },
};

function latencySLI(s, thresholdLabel) {
  const good = s.buckets[thresholdLabel];
  return good / s.total;
}

const sli = latencySLI(sample, '0.3');
console.log('Latency SLI (<300ms): ' + (sli * 100).toFixed(2) + '%');
console.log('Meets 99% SLO? ' + (sli >= 0.99));

Quick Check

Test your understanding of cardinality and the RED method.

Recap

You can now expose meaningful application metrics in Prometheus format and reason about them with the RED method.

  • Counter, Gauge, Histogram are the core metric types; prom-client manages a registry and renders the exposition format.
  • RED = Rate, Errors, Duration — all three fall out of instrumenting the request lifecycle once, in a single middleware.
  • Rate comes from a counter via rate(); Errors are that counter filtered by status_code=~"5.."; Duration comes from a histogram via histogram_quantile().
  • Always label by route template, never raw URLs, to control cardinality.
  • Expose metrics at /metrics and let Prometheus scrape it; keep the endpoint internal.
  • Turn RED metrics into SLIs (good/valid events), set SLOs, and track your error budget.

Frequently asked questions

Is the “Exposing Application Metrics and the RED Method” lesson free?

Yes — the full text of “Exposing Application Metrics and the RED Method” is free to read here on the web, and the Node.js Backend Development Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Node.js Backend Development Bootcamp course, upgrade to CoddyKit PRO.

What will I learn in “Exposing Application Metrics and the RED Method”?

Publish rate, errors, and duration metrics in Prometheus format and define meaningful SLIs. You practise Node.js Backend Development Bootcamp with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Node.js Backend Development Bootcamp?

No prior experience is required. Node.js Backend Development Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Exposing Application Metrics and the RED Method” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Node.js Backend Development Bootcamp lesson?

Yes. Every Node.js Backend Development Bootcamp lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Structured Logging with Correlation IDs
  2. Distributed Tracing with OpenTelemetry Spans
  3. Exposing Application Metrics and the RED Method
  4. Context Propagation with AsyncLocalStorage
← Back to Node.js Backend Development Bootcamp