exp-correlator: Async Context and Express Correlation ID

raw JSON →
1.0.0 verified Thu Apr 23 auth: no javascript

exp-correlator is a Node.js library designed to manage and propagate a correlation ID across asynchronous operations and within Express.js applications. It leverages Node.js's `async_hooks` to automatically track a unique identifier through various async calls, eliminating the need to explicitly pass the ID between functions. The current stable version is 1.0.0, and while a specific release cadence isn't stated, active GitHub workflows suggest ongoing maintenance. Its primary differentiator is the use of `async_hooks` for transparent context propagation, integrating seamlessly as an Express middleware or through a standalone async handler, and providing direct examples for integration with logging libraries like Pino and HTTP clients like exp-fetch.

error TypeError: Cannot read properties of undefined (reading 'use')
cause The Express `app` instance was not properly initialized or is not available when `app.use(middleware)` is called.
fix
Ensure const app = express(); is executed before attempting to apply exp-correlator's middleware.
error correlationId is consistently 'undefined' in logs or when retrieved by getId()
cause The async context for the correlation ID was not properly established, or it was lost due to an unhandled async boundary (e.g., certain event emitter callbacks, `setImmediate` without proper async context propagation).
fix
Verify that the middleware is correctly applied to your Express app or that attachCorrelationIdHandler wraps the entry point of your async operation. Review complex async patterns to ensure context is not inadvertently broken.
error Error: `exp-correlator` requires Node.js version `14.17` or higher.
cause The application is running on an unsupported Node.js version, which lacks necessary `async_hooks` features or stability.
fix
Upgrade your Node.js runtime to version 14.17 or newer to ensure compatibility and correct functionality.
gotcha Reliance on `async_hooks` for context propagation means `exp-correlator` requires Node.js `v14.17` or higher. Running on older Node.js versions will result in runtime errors.
fix Upgrade your Node.js runtime to version `14.17` or newer.
gotcha If `getId()` returns `undefined`, it indicates that the code is executing outside an `exp-correlator` context initialized by either the `middleware` or `attachCorrelationIdHandler`. This often happens with event emitters or certain callback patterns that inadvertently break the async flow.
fix Ensure all code paths intended to access a correlation ID are wrapped by `attachCorrelationIdHandler` or are part of an Express request handled by `middleware`. Debug complex async patterns to confirm context is maintained.
gotcha While powerful for transparent context management, `async_hooks` can introduce a slight performance overhead in extremely high-throughput applications or those with deeply nested and frequently invoked async operations. Monitor CPU and memory usage under load.
fix Profile your application under anticipated load. For extreme performance-critical paths where correlation IDs are not strictly necessary, consider opting out or using alternative, explicit passing mechanisms if `async_hooks` overhead is problematic.
npm install exp-correlator
yarn add exp-correlator
pnpm add exp-correlator

Demonstrates how to integrate `exp-correlator` as an Express middleware to automatically track and log correlation IDs across request handlers and nested asynchronous operations without explicitly passing the ID.

const express = require('express');
const { middleware, getId } = require('exp-correlator');
const app = express();
const port = 3000;

const logMessage = async (msg) => {
  const correlationId = getId(); 
  console.log({ timestamp: new Date().toISOString(), correlationId, msg });
};

// Simulate an external call that needs the correlation ID
const callToExternalSystem = async () => {
  const correlationId = getId(); 
  console.log({ timestamp: new Date().toISOString(), context: 'external-call', correlationId, message: 'Making external request' });
  // In a real app, you'd add correlationId to headers, e.g., 'correlation-id'
  await new Promise(resolve => setTimeout(resolve, 100)); // Simulate network delay
};

app.use(middleware); // Apply the correlation ID middleware to all incoming requests

app.get('/', async (req, res) => {
  const initialCorrelationId = getId();
  await logMessage("Request received for /");
  await callToExternalSystem();
  await logMessage("After external system call");
  res.json({ message: 'Hello World!', correlationId: initialCorrelationId });
});

app.get('/test', async (req, res) => {
  const initialCorrelationId = getId();
  await logMessage("Request received for /test");
  res.json({ message: 'Test endpoint', correlationId: initialCorrelationId });
});

app.listen(port, () => {
  console.log(`Server listening on http://localhost:${port}`);
  console.log('Try: curl -H "correlation-id: my-custom-id" http://localhost:3000');
  console.log('Or: curl http://localhost:3000/test');
});