@fastify/request-context
Request-scoped storage for Fastify apps, built on Node's AsyncLocalStorage, so any hook or handler can read and write per-request state without threading it through function arguments.
Repository Health
Technical Analysis
@fastify/request-context is an official Fastify plugin that gives every incoming HTTP request its own isolated storage container, backed by Node.js’s AsyncLocalStorage (with an AsyncResource bridge to keep the context alive across event-driven boundaries like body parsing). Once registered, any code running within the lifecycle of a request — hooks, route handlers, or functions several layers deep — can call requestContext.get(key) and requestContext.set(key, value) without the caller having to pass a context object down the call stack.
The plugin decorates both the Fastify instance and the request object with a requestContext accessor, and also exports a standalone requestContext singleton that works identically from imported modules, which is useful for loggers, service classes, or utility functions that don’t have direct access to req. A common pattern is stashing a request-scoped child logger or the authenticated user’s identity at the top of the request lifecycle, then reading it anywhere downstream — including inside try/catch blocks and error handlers — without re-fetching or re-validating that data.
Because it hooks into Fastify’s lifecycle (defaulting to onRequest, but configurable to any hook), and re-enters the async context explicitly at preValidation to work around a Node.js quirk where body-parsing emits events in a different async context, it correctly preserves per-request isolation even under concurrent load — a guarantee that naive global-variable or singleton-based request state cannot offer.
What You Get
- A
requestContextobject withget(key),set(key, value), andgetStore()methods, available as a Fastify instance decorator, a request decorator, and a standalone importable singleton - Configurable lifecycle hook binding (
onRequestby default, or any Fastify hook) so context initialization happens exactly where you need it defaultStoreValuessupport as either a static object or a per-request factory function that receives theFastifyRequest, for seeding context like a request ID or default user- A
createAsyncResourceoverride for supplying a customAsyncResourcesubclass, for advanced integration with other async-context-aware tooling - First-class TypeScript support via declaration merging on the
RequestContextDatainterface, soget/setcalls are typed to your app’s own context shape - An
asyncLocalStorageexport for manually running code inside a request context in tests or background workers, outside of an actual HTTP request
Common Use Cases
- Attaching a request-scoped child logger (with request ID, user ID, etc. already bound) at
onRequestand retrieving it from deep inside business logic without passing it as a parameter - Storing the authenticated user or tenant identity once during auth middleware and reading it from any downstream hook, handler, or service function
- Correlating request IDs across async operations for tracing/observability, without an APM agent’s own context propagation
- Sharing per-request state between plugins that don’t otherwise share a call stack (e.g. an error-handling plugin reading data set by an unrelated route hook)
- Simulating request context in unit tests or background jobs by wrapping code in
asyncLocalStorage.run()so context-dependent functions work outside a live request
Under The Hood
Architecture
The entire plugin lives in a single ~70-line index.js: it wraps a plain object (requestContext) exposing get/set/getStore, backed by a module-level AsyncLocalStorage instance, and is registered as a Fastify plugin via fastify-plugin (fp) so its decorators escape Fastify’s normal plugin encapsulation and are visible everywhere. On the configured hook (onRequest by default), it calls asyncLocalStorage.run() with a fresh store seeded from defaultStoreValues, and stashes an AsyncResource on the request via a private symbol so a second hook — preValidation — can explicitly re-enter that async context. That second hook exists specifically to counteract a documented Node.js async-context quirk where body-parsing and other EventEmitter-driven work can resume in a different async context than the one that started the request, which would otherwise silently break context propagation past preParsing.
Tech Stack
Zero runtime dependencies beyond fastify-plugin (^6.0.0) and Node’s built-in node:async_hooks module (AsyncLocalStorage, AsyncResource) — no polyfills or userland async-context shims. It ships as CommonJS with a hand-written types/index.d.ts that uses TypeScript’s module-augmentation pattern so consumers declare their own RequestContextData shape and get fully typed get/set calls. Peer compatibility is pinned per major version against specific Fastify major releases (v5 for the current major), and dev tooling includes neostandard/ESLint for style, c8 for coverage, and tstyche for dedicated TypeScript type-assertion tests.
Code Quality
The test suite (three spec files totalling over 1,000 lines, plus shared test/internal helpers for app initialization and a watcher service) covers both direct unit behavior and full end-to-end Fastify request flows, and npm test enforces c8 --100 — 100% statement/branch coverage is a hard requirement, not a target. TypeScript types are separately verified via tstyche type-assertion tests (types/index.tst.ts) rather than just compiling, and CI runs through Fastify’s shared reusable plugins-ci.yml workflow with linting and license-checking enabled. No swallowed errors or silent fallbacks were found in the small codebase; the only defensive branch is a null-store guard in set().
API Design
The public surface is deliberately minimal — one register() call, three methods (get, set, getStore), and three options (hook, defaultStoreValues, createAsyncResource) — which keeps the learning curve low for a library whose underlying mechanism (AsyncLocalStorage plus manual AsyncResource bridging) is genuinely subtle. The TypeScript-first design, where consumers augment a shared RequestContextData interface once and get typed access everywhere, avoids the common alternative of scattering as any casts around a generic context object. The README documents the async-context edge cases explicitly rather than leaving them as a support burden, which is unusual rigor for a small utility plugin.
Used by 2 apps in this directory
Dittofeed
Marketing · Automation
Open-source omni-channel customer engagement platform for automating transactional and marketing messages via email, SMS, WhatsApp, Slack, and mobile push.
Infisical
Security · Devops
The open-source platform for secrets, certificates, privileged access, and AI agent security — all in one self-hostable system.