One middleware. Access request context — correlation ID, IP, method, path, duration, custom fields — from anywhere in your call stack without passing
reqaround.
Uses Node.js AsyncLocalStorage under the hood. Zero dependencies. Works with Express 4 and 5.
// Without this library — you pass req through every layer
router.get('/orders', async (req, res) => {
const orders = await orderService.list(req); // need req for logging
await emailService.notify(req, orders); // need req for correlation ID
await auditService.log(req, 'orders.listed'); // need req everywhere
});// With express-correlation-context — context is available anywhere
router.get('/orders', async (_req, res) => {
const orders = await orderService.list(); // no req needed
await emailService.notify(orders); // clean signatures
await auditService.log('orders.listed'); // context flows automatically
});
// Deep inside orderService, emailService, auditService:
import { getContext, getCorrelationId } from 'express-correlation-context';
function logSomething() {
console.log({ correlationId: getCorrelationId() }); // just works
}npm install express-correlation-contextNo peer dependencies beyond Express itself.
import express from 'express';
import { correlationContext, getContext, getCorrelationId, setContext } from 'express-correlation-context';
const app = express();
// Register once — before your routes
app.use(correlationContext());
app.get('/hello', (req, res) => {
const ctx = getContext();
res.json({
correlationId: ctx?.correlationId, // UUID, auto-generated or from header
duration: ctx?.duration(), // ms elapsed since request started
ip: ctx?.ip,
method: ctx?.method,
path: ctx?.path,
});
});Every request automatically gets:
- A correlation ID (read from
x-correlation-idheader or auto-generated UUID) - IP, method, path, user-agent captured from the request
- A
duration()function that returns ms elapsed since the request started - The correlation ID echoed back in the response header
Express middleware factory. Register it once before your routes.
app.use(correlationContext({
header: 'x-correlation-id', // header to read/write. Default: 'x-correlation-id'
generateId: () => myCustomUUID(), // custom ID generator. Default: crypto.randomUUID()
echoHeader: true, // echo ID in response header. Default: true
onContext: (ctx, req) => { // hook to enrich context after creation
ctx.userId = (req as any).user?.id;
},
}));Returns the full context object for the current request, or null if called outside a request.
const ctx = getContext();
ctx?.correlationId // string
ctx?.startTime // Unix ms
ctx?.duration() // ms elapsed
ctx?.ip // client IP (respects x-forwarded-for)
ctx?.method // 'GET', 'POST', etc.
ctx?.path // '/api/orders'
ctx?.userAgent // 'Mozilla/5.0...'
ctx?.userId // any custom field set via setContext()Shorthand for getContext()?.correlationId.
import { getCorrelationId } from 'express-correlation-context';
logger.info('Processing job', { correlationId: getCorrelationId() });Set a custom field on the current request context. Throws if called outside a request.
// In an auth middleware
setContext('userId', decoded.sub);
setContext('tenantId', decoded.tenant);
// Anywhere later in the same request
const ctx = getContext();
console.log(ctx?.userId); // set earlier
console.log(ctx?.tenantId); // set earlier| Option | Type | Default | Description |
|---|---|---|---|
header |
string |
'x-correlation-id' |
Header name to read and write the correlation ID |
generateId |
() => string |
crypto.randomUUID() |
Custom ID generator (e.g. nanoid, cuid) |
echoHeader |
boolean |
true |
Whether to echo the ID back in the response header |
onContext |
(ctx, req) => void |
undefined |
Hook to enrich context after creation (e.g. extract userId from JWT) |
import pino from 'pino';
import { getContext } from 'express-correlation-context';
const baseLogger = pino();
// Wrap pino so every log line includes correlationId automatically
export const logger = {
info: (msg: string, data?: object) =>
baseLogger.info({ correlationId: getContext()?.correlationId, ...data }, msg),
error: (msg: string, data?: object) =>
baseLogger.error({ correlationId: getContext()?.correlationId, ...data }, msg),
};
// Usage — no req needed
logger.info('Order created', { orderId: 'ord_123' });
// → { correlationId: 'a1b2-...', orderId: 'ord_123', msg: 'Order created' }app.use(jwtMiddleware); // sets req.user
app.use(
correlationContext({
onContext: (ctx, req) => {
const authed = req as Request & { user?: { id: string; role: string } };
if (authed.user) {
ctx.userId = authed.user.id;
ctx.role = authed.user.role;
}
},
}),
);
// Now available everywhere
const ctx = getContext();
console.log(ctx?.userId); // 'u_abc123'
console.log(ctx?.role); // 'admin'app.use(
correlationContext({
onContext: (ctx, req) => {
ctx.tenantId = req.headers['x-tenant-id'] ?? 'unknown';
},
}),
);
// Inside any service
async function createOrder(data: OrderData) {
const ctx = getContext();
await db.orders.create({ ...data, tenantId: ctx?.tenantId });
}app.use(correlationContext());
app.use((_req, res, next) => {
res.on('finish', () => {
const duration = getContext()?.duration();
if (duration !== undefined) {
res.setHeader('x-response-time', `${duration}ms`);
}
});
next();
});// main.ts
import { correlationContext } from 'express-correlation-context';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(correlationContext());
await app.listen(3000);
}All exports are fully typed. Extend CorrelationContext for custom fields:
import { getContext, CorrelationContext } from 'express-correlation-context';
interface AppContext extends CorrelationContext {
userId: string;
tenantId: string;
}
const ctx = getContext() as AppContext | null;
ctx?.userId; // typed
ctx?.tenantId; // typedAsyncLocalStorage (available since Node.js 12.17, stable since 16) creates an isolated storage slot that propagates automatically through:
async/awaitPromise.then()setTimeout/setIntervalEventEmittercallbacks- Any other async continuation in the same request chain
Each incoming request gets its own isolated store. Concurrent requests never see each other's context — guaranteed by the runtime.
MIT © Saifuddin Tipu