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 {
  Injectable,
  PipeTransform,
  ArgumentMetadata,

A custom validation pipe in NestJS

validation dto recursion
Intermediate 10 steps
typescript
import { Component, inject, effect } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';

Debounced search that syncs to the URL in Angular

signals reactive-forms debouncing
Advanced 8 steps
typescript
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';

Wiring validation and Swagger docs in NestJS

validation openapi decorators
Intermediate 8 steps
javascript
const ROLE_PERMISSIONS = {
  admin: ['users:read', 'users:write', 'billing:read', 'billing:write'],
  manager: ['users:read', 'billing:read'],
  member: ['users:read'],

Role-based permissions middleware in Express

authorization middleware rbac
Intermediate 9 steps
typescript
import { Component, Input } from '@angular/core';
 
interface Order {
  id: string;

How Angular ICU plurals localize an order summary

i18n pluralization standalone-component
Intermediate 8 steps
typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, catchError, concatMap, finalize } from 'rxjs';
import { DataSource, QueryRunner } from 'typeorm';
 

Wrapping requests in a transaction with NestJS

interceptors transactions rxjs
Advanced 7 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