typescript 42 lines · 5 steps

Recording HTTP metrics with a NestJS interceptor

A NestJS interceptor times every HTTP request and increments Prometheus counters when the response finishes.

Explained by highlit
1import {
2 CallHandler,
3 ExecutionContext,
4 Injectable,
5 NestInterceptor,
6} from '@nestjs/common';
7import { HttpArgumentsHost } from '@nestjs/common/interfaces';
8import { Observable } from 'rxjs';
9import { finalize } from 'rxjs/operators';
10import { InjectMetric } from '@willsoto/nestjs-prometheus';
11import { Counter, Histogram } from 'prom-client';
12import { Request, Response } from 'express';
13 
14@Injectable()
15export class MetricsInterceptor implements NestInterceptor {
16 constructor(
17 @InjectMetric('http_request_duration_seconds')
18 private readonly duration: Histogram<string>,
19 @InjectMetric('http_requests_total')
20 private readonly total: Counter<string>,
21 ) {}
22 
23 intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
24 if (context.getType() !== 'http') {
25 return next.handle();
26 }
27 
28 const http: HttpArgumentsHost = context.switchToHttp();
29 const req = http.getRequest<Request>();
30 const res = http.getResponse<Response>();
31 const route = req.route?.path ?? req.path;
32 const stopTimer = this.duration.startTimer({ method: req.method, route });
33 
34 return next.handle().pipe(
35 finalize(() => {
36 const status = String(res.statusCode);
37 stopTimer({ status });
38 this.total.inc({ method: req.method, route, status });
39 }),
40 );
41 }
42}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Interceptors wrap the request lifecycle, letting you run code before and after the handler responds.
  2. 2The rxjs finalize operator fires on both success and error, making it ideal for cleanup like stopping a timer.
  3. 3Checking context.getType() keeps HTTP-only logic from breaking on non-HTTP transports.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
python
import time
import uuid
 
from django.utils.deprecation import MiddlewareMixin

Attaching per-request context in Django

middleware request lifecycle multi-tenancy
Intermediate 7 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps
typescript
import { useEffect, useState } from "react";
 
interface Section {
  id: string;

Building a scroll-spy hook in React

custom-hooks intersectionobserver dom-observation
Intermediate 8 steps

Share this explainer

Here's the card — post it anywhere.

Recording HTTP metrics with a NestJS interceptor — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code