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-idyarn add @evanion/nestjs-correlation-idpnpm add @evanion/nestjs-correlation-idRegister 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()returnsundefined.- Nest runs middleware in the order
configure()applies it, and the correlation context opens inCorrelationIdMiddleware. Apply your own middleware that reads the id in a laterconsumer.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-0042The 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-4f1c9aunderx-correlation-id, the lowercase name Node gives every incoming header - once for a request with no header, where the generator returns
orders-0f3a2bso 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'
.(); // -> undefinedThe 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.