Skip to Content
Correlation IDAPI Reference

API reference

@evanion/nestjs-correlation-id 2.1.0 and its patch releases up to 2.1.1, the version on npm. Values on this page were produced by running the source at 77650ee, a later commit than the 2.1.0 tag, and are not re-executed.

@evanion/nestjs-correlation-id exports one module, one middleware, one service, one function that configures HttpModule, one configuration interface and four constants.

CorrelationModule

CorrelationModule provides CorrelationService and the resolved configuration to the whole application.

class CorrelationModule { static forRoot(config?: Partial<CorrelationConfig>): DynamicModule; }
Signature — a shape, not a call

forRoot() fills in every unset field of config and returns the module, registered global: true. It provides and exports CorrelationService, and the configuration under CORRELATION_CONFIG_TOKEN. Call it once, in the root module. A second call creates a second CorrelationService and configuration, and a provider holding the copy the middleware did not use reads undefined inside every request.

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

CorrelationIdMiddleware

CorrelationIdMiddleware opens a correlation context around every request it is applied to.

class CorrelationIdMiddleware implements NestMiddleware { constructor( correlationService: CorrelationService, correlationConfig: CorrelationConfig, ); use( req: IncomingMessage, res: ServerResponse, next: (error?: unknown) => void, ): void; }
Signature — a shape, not a call

Per request it does five things, in order:

  1. Reads the configured header, case-insensitively. A repeated header arrives as an array or as one comma-joined string, and both become one comma-joined string, which the default validator rejects.
  2. Reuses that value when validate accepts it, and calls the generator otherwise.
  3. Writes the id onto req.headers when the request carried no header, so a proxy or an access logger reading the raw header sees the same id. A header that arrived stays as it came in, including one validate rejected.
  4. Sets the response header, unless something already set it.
  5. Calls next inside CorrelationService.run(id, next).

req and res are typed against node:http, so the class runs under the Express and the Fastify adapters. Apply middleware that reads the id after this one.

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

CorrelationService

CorrelationService reads, replaces and generates the correlation id, and opens a correlation context. A correlation context is the callback passed to run(), plus everything it calls or awaits.

@Injectable() class CorrelationService { constructor(correlationConfig: CorrelationConfig); run<T>(correlationId: string, callback: () => T): T; getCorrelationId(): string | undefined; setCorrelationId(correlationId: string): void; generate(): string; }
Signature — a shape, not a call
MethodWhat it does
run(id, callback)runs callback in a correlation context and returns what it returns
getCorrelationId()the surrounding context’s id, or undefined outside one; synchronous
setCorrelationId(id)replaces the surrounding context’s id; throws outside one
generate()a fresh id from the configured generator

The service is a singleton over AsyncLocalStorage, and injecting it leaves the scope of the provider holding it unchanged. Call setCorrelationId in a context opened before the right id was known, such as a queue consumer that wraps every handler in run(generate(), …) and reads the message’s id afterwards. It leaves the response header with the id the middleware wrote on arrival. The code below builds a worker with no HTTP server, whose generator returns orders-7c41d2:

import { } from '@nestjs/core'; import { , , } from '@evanion/nestjs-correlation-id'; const = await .( .({ : () => 'orders-7c41d2' }), { : false }, ); const = .(); const = .(.(), () => .(), ); let : string | undefined; .('orders-0f3a2b', () => { .('storefront-4f1c9a'); = .(); }); let = ''; try { .('storefront-4f1c9a'); } catch () { = ( as Error).; } await .(); ; // -> 'orders-7c41d2' ; // -> 'storefront-4f1c9a' ; // -> 'setCorrelationId() was called outside a correlation context. Apply CorrelationIdMiddleware, or wrap the work in CorrelationService.run().'

withCorrelation(config?)

withCorrelation() returns options for HttpModule.registerAsync that send the current correlation id on every outgoing request.

function withCorrelation(config?: HttpModuleOptions): HttpModuleAsyncOptions;
Signature — a shape, not a call

config reaches the axios instance unchanged. The id is attached by an axios request interceptor, registered as an extra provider under CORRELATION_AXIOS_INTERCEPTOR, which reads the context as each request goes out. No header is attached outside a context.

It requires CorrelationModule.forRoot() somewhere in the application; without it Nest fails at boot with Nest can't resolve dependencies of the @evanion/nestjs-correlation-id:AXIOS_INTERCEPTOR (AXIOS_INSTANCE_TOKEN, ?, @evanion/nestjs-correlation-id:CORRELATION_CONFIG). The provider reaches the axios instance through 'AXIOS_INSTANCE_TOKEN', the string token @nestjs/axios registers it under, and the package imports @nestjs/axios for its types only. An application that never calls withCorrelation does not need @nestjs/axios installed.

In the code below, StockClient is the application’s own HttpService consumer, shown on Forward the id to the next service, and StockModule registers it:

import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { withCorrelation } from '@evanion/nestjs-correlation-id'; import { StockClient } from './stock.client.js'; @Module({ imports: [HttpModule.registerAsync(withCorrelation({ timeout: 5000 }))], providers: [StockClient], exports: [StockClient], }) export class StockModule {}

CorrelationConfig

CorrelationConfig is the configuration forRoot() resolves and provides under CORRELATION_CONFIG_TOKEN.

interface CorrelationConfig { header: string; generator: () => string; validate?: (value: string) => boolean; }
Signature — a shape, not a call
FieldWhat it is
headerthe header read from the request and written to the response, in the casing sent on the wire
generatorgenerates an id when the request carried none that validate accepts
validatedecides whether an incoming id is reused; defaults to DEFAULT_CORRELATION_ID_VALIDATOR

forRoot() takes a Partial<CorrelationConfig> and fills in every field, validate included, so a provider injecting the token never sees a partial object. validate is optional in the type because CorrelationIdMiddleware falls back to DEFAULT_CORRELATION_ID_VALIDATOR for a configuration built without forRoot(), as a unit test builds one, that has no validate.

import { } from '@nestjs/core'; import { , , type CorrelationConfig, } from '@evanion/nestjs-correlation-id'; const = await .( .({ : 'X-Request-Id', : () => `orders-${.().(0, 6)}`, : () => /^(storefront|orders|stock)-[0-9a-f]{6}$/.(), }), { : false }, ); const = .<CorrelationConfig>(); await .(); .; // -> 'X-Request-Id' .?.('storefront-4f1c9a'); // -> true .?.('storefront-4f1c9a, orders-0f3a2b'); // -> false .?.(.()); // -> true

CORRELATION_ID_HEADER

CORRELATION_ID_HEADER is the header forRoot() uses when config.header is not set.

const CORRELATION_ID_HEADER = 'X-Correlation-Id';
Signature — a shape, not a call

No correlation header is registered with the Internet Assigned Numbers Authority (IANA). X-Correlation-Id and X-Request-Id are the two in common use.

import { } from '@evanion/nestjs-correlation-id'; ; // -> 'X-Correlation-Id'

CORRELATION_CONFIG_TOKEN

CORRELATION_CONFIG_TOKEN is the injection token of the resolved CorrelationConfig.

const CORRELATION_CONFIG_TOKEN = '@evanion/nestjs-correlation-id:CORRELATION_CONFIG';
Signature — a shape, not a call

The token is namespaced because forRoot() registers it in a global: true module, where a bare 'CORRELATION_CONFIG' would collide with any other package that chose the same string. It is a string because a Symbol() is unique per copy of the module: two copies of the package would mint two tokens, and Nest would fail to resolve one of them.

import { } from '@evanion/nestjs-correlation-id'; ; // -> '@evanion/nestjs-correlation-id:CORRELATION_CONFIG'

Inject it with @Inject. RestockJob, a queue consumer in Baize’s orders service, injects it to check an id a queue message carried with the same validate the middleware applies. StockClient is the service’s own HttpService consumer, shown on Forward the id to the next service. Carry the id through a job explains the consumer.

import { Inject, Injectable } from '@nestjs/common'; import { CORRELATION_CONFIG_TOKEN, CorrelationService, type CorrelationConfig, } from '@evanion/nestjs-correlation-id'; import { StockClient } from './stock.client.js'; export interface RestockMessage { game: string; correlationId?: string; } @Injectable() export class RestockJob { constructor( private readonly correlation: CorrelationService, @Inject(CORRELATION_CONFIG_TOKEN) private readonly config: CorrelationConfig, private readonly stock: StockClient, ) {} handle(message: RestockMessage) { const carried = message.correlationId; const id = carried && this.config.validate?.(carried) ? carried : this.correlation.generate(); return this.correlation.run(id, () => this.stock.level(message.game)); } }

CORRELATION_AXIOS_INTERCEPTOR

CORRELATION_AXIOS_INTERCEPTOR is the token of the provider withCorrelation() registers to attach its interceptor to the axios instance.

const CORRELATION_AXIOS_INTERCEPTOR = '@evanion/nestjs-correlation-id:AXIOS_INTERCEPTOR';
Signature — a shape, not a call

Nothing injects it. The provider exists so that Nest runs the factory that registers the interceptor.

import { } from '@evanion/nestjs-correlation-id'; ; // -> '@evanion/nestjs-correlation-id:AXIOS_INTERCEPTOR'

DEFAULT_CORRELATION_ID_VALIDATOR

DEFAULT_CORRELATION_ID_VALIDATOR accepts an incoming id of 1 to 128 characters, each a letter from A to Z in either case, a digit, an underscore, a dot, a colon or a hyphen, and rejects everything else.

const DEFAULT_CORRELATION_ID_VALIDATOR: (value: string) => boolean;
Signature — a shape, not a call

An accepted id reaches the response header and the application’s logs. Node rejects a carriage return (CR) or line feed (LF) in a header, incoming and outgoing, so the validator’s CR and LF refusal covers an id from another source, such as a queue message, that your code passes through it. The 128-character cap bounds what one request appends to every log line it touches. A universally unique identifier (UUID) from randomUUID is 36 characters.

import { } from '@evanion/nestjs-correlation-id'; ('storefront-4f1c9a'); // -> true ('0f3a2b7c-41d2-4e3a-9f55-2c1d4e6a8b90'); // -> true ('storefront-4f1c9a, orders-0f3a2b'); // -> false ('orders-0f3a2b\r\nX-Admin: 1'); // -> false ('a'.(129)); // -> false
Last updated on