@tony4ork/nestjs-context
v0.0.1
Published
Shared request/job context (AsyncLocalStorage) for Nestjs applications
Readme
@tony4ork/nestjs-context
Shared context for each unit of work (HTTP request, job, cron), built on Node's AsyncLocalStorage.
This package is the "thread" that ties signals together: @tony4ork/logger reads from it so every log line carries request_id, user.id; the Login SDK's guard writes userId into it after authentication; @tony4ork/monitoring (later) and Sentry reuse this same context instead of creating their own.
It's kept separate from the logger because many packages need the context without needing the logger (Login SDK, Payment, internal HTTP client). If it lived inside the logger, those SDKs would be forced to depend on the logger just to read userId.
Installation
# pnpm
pnpm add @tony4ork/nestjs-context
# yarn
yarn add @tony4ork/nestjs-context
# npm
npm install @tony4ork/nestjs-contextRequirements:
- Node >=18.18.
@nestjs/common^10.0.0 or ^11.0.0 as a peer dependency (install it in your project if it isn't already).
If your project uses @tony4ork/nest-core, there's nothing else to do: bootstrap() already wires up the context middleware ahead of every other middleware. The section below is for projects that don't use nest-core yet.
// src/app.module.ts
@Module({
imports: [
ContextModule.forRoot(), // put it first
LoggerIntegration,
AuthIntegration,
// ...
],
})
export class AppModule {}API
| Function | Use when |
| ------------------------------------------------------ | ----------------------------------------------------------------------------- |
| RequestContext.requestId(), .userId(), .get(key) | Reading the current context |
| RequestContext.set(key, value), .assign({...}) | Attaching an extra value (returns false if there is no context) |
| RequestContext.snapshot() | Getting a copy to send to Sentry, an audit log... |
| RequestContext.fork(extra, fn) | A child context for each item in a batch |
| runWithContext(ctx, fn) | Creating a context somewhere with no HTTP request: worker, cron, script |
| contextHeaders() | Headers to attach when calling an internal service |
| contextForJob() | Data to attach when pushing a job onto a queue |
| getRequestContext(req) | Reading the context off the request object (for code outside the async chain) |
Examples
The Login SDK's guard attaches userId:
canActivate(ctx: ExecutionContext) {
const user = this.verify(ctx);
RequestContext.set('userId', user.id);
return true;
}Pushing and processing a job, keeping the same request id:
// On the pushing side
await this.queue.add('send-receipt', { orderId, _ctx: contextForJob() });
// On the worker side (BullMQ)
async process(job: Job) {
return runWithContext({ ...job.data._ctx, jobId: job.id }, () => this.handle(job));
}Calling an internal service:
await this.http.post(url, body, { headers: { ...contextHeaders() } });Cron:
@Cron('0 * * * *')
syncRates() {
return runWithContext({}, () => this.doSync()); // generates a new requestId
}Configuration
ContextModule.forRoot({
header: "X-Request-ID", // default; header name matching is case-insensitive
trustIncomingRequestId: true, // set false if the service receives traffic directly from the internet
exposeHeader: true, // return the request id to the client via a response header
});A request id received from outside is validated (at most 128 characters, only letters, digits and . _ : -); an invalid value is discarded and a new id is generated, to prevent log injection.
Notes
Only put small, identifying values into the context. Don't put entities, large objects, tokens, or passwords: everything in the context shows up in every log line.
Multiple copies of @tony4ork/nestjs-context in the same node_modules still share a single storage (keyed via Symbol.for), so a version mismatch won't lose the context. Even so, it's best to keep a single version.
Works with both Express and Fastify, and with NestJS 10 and 11.
For package and SDK authors
@tony4ork/nestjs-context must be a peerDependency of any package that uses it, not a regular dependency:
// SDK's package.json
"peerDependencies": { "@tony4ork/nestjs-context": "^0.2.0" }Product projects should declare @tony4ork/nestjs-context directly so only one copy is installed. If multiple copies still end up loaded (wrong install, version mismatch), the package prints a warning on startup:
(node:1) [COMPANY_CONTEXT_DUPLICATE] Warning: Detected 2 copies of @tony4ork/nestjs-context (different versions) ...Thanks to Symbol.for, the copies still share the same storage so things usually don't break right away, but it should still be fixed: run pnpm why @tony4ork/nestjs-context to find which package is pulling in the second copy.
