nestjs-cls

Continuation-local storage for NestJS, built on AsyncLocalStorage and wired into Nest's own DI system.

Library
npm
v6.3.0
700stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
75/100Good
Development Activity76
Maintenance80
Community48
Maturity56
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture85
Code Quality88
Innovation78
Learning Curve80

nestjs-cls gives NestJS applications a request-scoped, continuation-local storage (CLS) context without relying on Nest’s REQUEST-scoped providers. Built directly on Node’s native AsyncLocalStorage, it lets any part of a request’s call graph — controllers, guards, interceptors, services buried several layers deep, even code running outside an HTTP request such as a cron job or queue consumer — read and write shared state through a single injectable ClsService.

The module ships a ClsMiddleware, ClsGuard, and ClsInterceptor to initialize the context at whichever layer fits the transport (HTTP, GraphQL, WebSockets, microservices), plus a plugin system used by the companion nestjs-cls/transactional package to propagate a database transaction across services without threading it through every function signature. A separate Proxy Provider mechanism produces DI tokens that resolve lazily against the active CLS context, offered as a drop-in replacement for REQUEST-scoped providers in places NestJS doesn’t support them, such as Passport strategies and WebSocket gateways.

It is the de facto standard for request-context propagation in the NestJS ecosystem, with adapters published for TypeORM, Prisma, Drizzle, Knex, Kysely, Mongoose, MongoDB, and pg-promise transactions.

What You Get

  • ClsModule - forRoot/forRootAsync global setup plus forFeature/forFeatureAsync for registering Proxy Providers per module
  • ClsService - the injectable API for get/set/has/run/enter operations against the active context, with typed keys via RecursiveKeyOf
  • ClsMiddleware, ClsGuard, ClsInterceptor - three ways to initialize the CLS context depending on which NestJS layer your transport hits first
  • Proxy Providers - injectable proxies that resolve lazily against the CLS context, used to stand in for REQUEST-scoped providers in strategies, gateways, and queue consumers
  • A plugin system (ClsPluginBase) - the extension point the official Transactional plugin and eight database adapters (TypeORM, Prisma, Drizzle, Knex, Kysely, Mongoose, MongoDB, pg-promise) build on
  • Framework-agnostic transports - documented support for HTTP (Express/Fastify), GraphQL, WebSockets, and microservices

Common Use Cases

  • Request ID / correlation ID logging - stamp every log line in a request’s call graph with a shared request ID without passing it as a parameter
  • Multi-tenant database routing - store the resolved tenant’s DB connection once in the CLS context and read it from any service down the call stack
  • Transaction propagation - start a database transaction at the top of a use case and have every repository call underneath it join the same transaction via the Transactional plugin
  • Auth/role propagation to non-HTTP contexts - replace REQUEST-scoped providers in Passport strategies, cron handlers, and queue consumers with Proxy Providers backed by the CLS context
  • Current-user access deep in the service layer - avoid threading a user argument through every intermediate function call in a request

Under The Hood

Architecture The module builds a small dependency graph around a single shared AsyncLocalStorage instance held by ClsService (packages/core/src/lib/cls.service.ts), which every other piece composes around rather than replaces. ClsModule (cls-module/cls.module.ts) wires ClsRootModule (creates the ALS instance and registers the chosen initializer) together with ClsPluginsModule, which turns declared plugins into additional Nest dynamic modules at forRoot time. Context initialization is decoupled from context access: ClsMiddleware, ClsGuard, and ClsInterceptor (cls-initializers/) are interchangeable entry points that call als.enterWith/als.run, while ClsService is the single read/write surface used everywhere downstream, so swapping how a request enters CLS never touches consumer code. The Proxy Provider subsystem (proxy-provider/proxy-provider-manager.ts) is architecturally separate again: it keeps a static Map of provider definitions and returns JS Proxy objects whose trap handlers resolve the real instance from the CLS store on every access, letting Nest’s DI graph hand out a proxy at bootstrap time before the real value could possibly exist. If AsyncLocalStorage itself were replaced, only ClsService’s constructor and runWith/enterWith would need to change — every initializer, plugin, and proxy consumer is written against the service’s public API, not the storage primitive.

Tech Stack A Yarn 4 (Berry) workspaces monorepo with packages/core (the published nestjs-cls package), packages/transactional, and eight packages/transactional-adapters/* packages for TypeORM, Prisma, Drizzle, Knex, Kysely, Mongoose, MongoDB, and pg-promise. Core has zero runtime dependencies beyond Node’s built-in async_hooks; @nestjs/common, @nestjs/core, reflect-metadata, and rxjs are declared as peer dependencies (supporting Nest 10 through 12), and the devDependencies pin parallel @nestjs/*10 aliases to run the test suite against both the 10.x and 12.x Nest APIs simultaneously. TypeScript 5.9 compiles under strict: true (with noImplicitAny relaxed), builds via plain tsc, and tests run on Jest 30 with ts-jest. A Docusaurus site under docs/ is built and versioned as its own workspace package, and monoweave handles the coordinated multi-package release/changelog process across the whole monorepo.

Code Quality 19 spec files sit alongside their source files (*.spec.ts co-located, not in a separate test/ tree, e.g. cls.service.spec.ts, proxy-provider-manager.spec.ts), and packages/core/test holds integration-style e2e suites exercising real Nest apps across Express, Fastify, GraphQL (Apollo and Mercurius), and WebSockets. Errors are explicit and typed rather than swallowed — ClsService#set/get throw descriptive Errors when no context is active, and the Proxy Provider system defines dedicated exception classes (ProxyProviderNotDecoratedException, ProxyProviderNotResolvedException) with static .create() factories rather than generic throws. ESLint 9 (flat config) plus Prettier enforce style, .dependency-cruiser.js enforces module-boundary rules across the monorepo, and GitHub Actions (.github/workflows/run-tests.workflow.yml) runs the suite on every push; dependabot.yml keeps dependencies current.

API Design The public surface is deliberately small: one module (ClsModule.forRoot), one service (ClsService), and a handful of initializers, so a typical integration is a single forRoot call plus one middleware/guard registration. Method overloads on get/set/has/run/enter let a caller pass either a single string key or a whole object, with TypeScript’s RecursiveKeyOf inferring valid dotted paths against a user-declared ClsStore interface — giving compile-time-checked access to a plain runtime map without a code generation step. The ifNested option (inherit/reuse/override) on run/enter names an otherwise easy-to-get-wrong behavior explicitly instead of leaving it as an implicit default, and the Proxy Provider API mirrors Nest’s own provider registration shape (useClass/useFactory) so it reads as a natural extension of Nest’s DI rather than a bolt-on concept.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search