Skip to main content

NestJS CLS

A continuation-local* storage module compatible with NestJS' dependency injection based on Node's AsyncLocalStorage.

Continuation-local storage allows to store state and propagate it throughout callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.

Use cases

Some common use cases that this library enables include:

  • Tracking the Request ID and other metadata for logging purposes
  • Keeping track of the user throughout the whole request
  • Making the dynamic Tenant database connection available everywhere in multi-tenant apps
  • Propagating the authentication level or role to restrict access to resources
  • Seamlessly propagating database transaction across services without breaking encapsulation and isolation by explicitly passing it around (Now available with the Transactional plugin)
  • Using "request" context in cases where actual REQUEST-scoped providers are not supported (passport strategies, cron controllers, websocket gateways, ...)

Most of these are to some extent solvable using REQUEST-scoped providers or passing the context as a parameter, but these solutions are often clunky and come with a whole lot of other issues.

info

* The name comes from the original implementation based on cls-hooked, which was since replaced by the native AsyncLocalStorage.

Motivation

NestJS is an amazing framework, but in the plethora of awesome built-in features, I still missed one.

I created this library to solve a specific use case, which was limiting access to only those records which had the same TenantId as the request's user in a central manner. The repository code automatically added a WHERE clause to each query, which made sure that other developers couldn't accidentally mix tenant data (all tenants' data were held in the same database) without extra effort.

AsyncLocalStorage is still fairly new and not many people know of its existence and benefits. Here's a nice talk from NodeConf about the history. I've invested a great deal of my personal time in making the use of it as pleasant as possible.

While the use of async_hooks is sometimes criticized for making Node run slower, in my experience, the introduced overhead is negligible compared to any IO operation (like a DB or external API call). If you want fast, use a compiled language.

Also, if you use some tracing library (like otel), it most likely already uses async_hooks under the hood, so you might as well use it to your advantage.

Highlights

New Version 4.0 brings support for Plugins which enable pre-built integrations with other libraries and frameworks. (See Migration guide for breaking changes).

Version 3.0 introduces Proxy Providers as an alternative to the imperative API. (Minor breaking changes were introduced, see Migration guide).

Version 2.0 brings advanced type safety and type inference. However, it requires features from typescript >= 4.4 - Namely allowing symbol members in interfaces. If you can't upgrade but still want to use this library, install version 1.6.2, which lacks the typing features.