Skip to Content
Correlation IDGetting Started

Unreleased changes

These pages document main, which has changes 2.1.1 does not. npm install @evanion/nestjs-correlation-id gives you 2.1.1, documented here. Values on these pages are produced by running the current source on every build.

Getting started

By the end of this page every request to your NestJS application carries a correlation id, and a handler reads it from CorrelationService.

Before you install nestjs-correlation-id

@evanion/nestjs-correlation-id needs:

  • A NestJS 12 application, meaning @nestjs/common ^12.0.0, on the Express or the Fastify adapter.
  • Node 20 or newer. The package ships only ES modules, so the minimum depends on your project’s module format:
    • An ES module project imports it on any Node 20.
    • A CommonJS project loads it through require(), which needs Node 20.19, 22.12 or newer. NestJS 12 needs the same versions in a CommonJS project.

Install nestjs-correlation-id

npm install @evanion/nestjs-correlation-id
yarn add @evanion/nestjs-correlation-id
pnpm add @evanion/nestjs-correlation-id

Register CorrelationModule and the middleware

CorrelationModule.forRoot() from @evanion/nestjs-correlation-id provides CorrelationService to every module, and CorrelationIdMiddleware opens a correlation context around each request. Register both in your root module:

import { MiddlewareConsumer, , NestModule } from '@nestjs/common'; import { , , } from '@evanion/nestjs-correlation-id'; @({ : [.()], }) export class implements NestModule { (: MiddlewareConsumer) { .().('*'); } }

forRoot() with no argument is the whole minimum configuration. The middleware then reads X-Correlation-Id and generates a missing id with randomUUID from node:crypto. It reuses an incoming id only when the id passes DEFAULT_CORRELATION_ID_VALIDATOR, which accepts 1 to 128 characters from the ASCII letters, the digits, _, ., : and -. forRoot() also takes three optional fields, header, generator and validate, that replace those defaults.

  • forRoot() registers a global module, so call it once, in the root module.
  • forRoutes('*') covers every route. On a route the middleware skips, getCorrelationId() returns undefined.
  • Nest runs middleware in the order configure() applies it, and the correlation context opens in CorrelationIdMiddleware. Apply your own middleware that reads the id in a later consumer.apply() call, or later in the same call’s argument list.

Read the request’s id in a handler

Inject CorrelationService from @evanion/nestjs-correlation-id and call getCorrelationId(). OrdersController in Baize’s orders service returns the id with the order and writes both to its log line:

import { Controller, Get, Logger, Param } from '@nestjs/common'; import { CorrelationService } from '@evanion/nestjs-correlation-id'; @Controller('orders') export class OrdersController { private readonly logger = new Logger(OrdersController.name); constructor(private readonly correlation: CorrelationService) {} @Get(':order') find(@Param('order') order: string) { const correlationId = this.correlation.getCorrelationId(); this.logger.log({ order, correlationId }); return { order, correlationId }; } }

getCorrelationId() is synchronous, and nothing passes the id to find(): the controller reads it from the context the middleware opened.

Check that the caller’s id comes back

To see @evanion/nestjs-correlation-id reuse a caller’s id, add controllers: [OrdersController] to the @Module() of AppModule and start the application. The request below assumes it listens on port 3000, the port nest new sets up. Send a request with an id:

curl -i -H 'X-Correlation-Id: storefront-4f1c9a' http://localhost:3000/orders/order-2026-0042

The response carries the header X-Correlation-Id: storefront-4f1c9a and the body {"order":"order-2026-0042","correlationId":"storefront-4f1c9a"}. Leave the header off and both carry a fresh universally unique identifier (UUID).

The code below checks the same result without a server, in a form a unit test can use. It builds CorrelationService and CorrelationIdMiddleware by hand, the two objects Nest builds in an application, and calls the middleware with stub request and response objects:

  • once for a request that carries storefront-4f1c9a under x-correlation-id, the lowercase name Node gives every incoming header
  • once for a request with no header, where the generator returns orders-0f3a2b so that the result is predictable
import { , , } from '@evanion/nestjs-correlation-id'; const = { : 'X-Correlation-Id', : () => 'orders-0f3a2b' }; const = new (); // The middleware opens its contexts on the service it is given. const = new (, ); // The middleware reads the request's headers and writes the id with setHeader. // It leaves a response header that something earlier already set, so it asks // getHeader first, and this stub answers that nothing did. `as never` lets the // stubs stand in for Node's full IncomingMessage and ServerResponse. const = { : () => , : () => }; const = (: <string, string>) => { let : string | undefined; .({ } as never, as never, () => { = .(); }); return ; }; ({ 'x-correlation-id': 'storefront-4f1c9a' }); // -> 'storefront-4f1c9a' ({}); // -> 'orders-0f3a2b' .(); // -> undefined

The configuration sets no validate, so the middleware applied DEFAULT_CORRELATION_ID_VALIDATOR, and storefront-4f1c9a passed it.

Send the id on to the stock service

Every request to your application now carries an id from @evanion/nestjs-correlation-id, and a handler reads it. Forward the id to the next service passes withCorrelation() to HttpModule, so Baize’s stock service receives the id the orders service was called with.

Last updated on