Introduction
Async local storage in NestJS enables scope-bound storage, allowing you to maintain context across asynchronous operations. It’s essential in tracing requests and handling per-request data without passing parameters around.
Setting Up Async Local Storage
To begin, install the necessary packages:
npm install @nestjs/common @nestjs/core reflect-metadata
Then, set up the async local storage middleware:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AsyncLocalStorage } from 'async_hooks';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const asyncLocalStorage = new AsyncLocalStorage();
app.use((req, res, next) => {
asyncLocalStorage.run(new Map(), () => {
next();
});
});
await app.listen(3000);
}
bootstrap();
Injecting and Using the Storage
Here’s how you can inject and use the storage:
import { Injectable, Scope } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
@Injectable({ scope: Scope.REQUEST })
export class MyService {
constructor(private asyncLocalStorage: AsyncLocalStorage) {}
myMethod() {
const store = this.asyncLocalStorage.getStore();
// Use the store
}
}
Providing Context
To provide context for dependencies, use interceptors or middleware:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { AsyncLocalStorage } from 'async_hooks';
@Injectable()
export class ContextInterceptor implements NestInterceptor {
constructor(private asyncLocalStorage: AsyncLocalStorage) {}
intercept(context: ExecutionContext, next: CallHandler): Observable {
const ctx = this.asyncLocalStorage.getStore();
if (ctx) {
ctx.set('key', 'value');
}
return next.handle();
}
}
Advanced Usage: Per-Request Providers
Create providers that are new for each request like this:
import { REQUEST } from '@nestjs/core';
import { Provider } from '@nestjs/common';
export const myRequestProvider: Provider = {
provide: 'MY_PROVIDER',
useFactory: (request) => new MyProvider(request),
inject: [REQUEST],
};
Integrating with Other Modules
To integrate async local storage with other modules:
import { DynamicModule, Module } from '@nestjs/common';
import { MyService } from './my.service';
@Module({})
export class MyModule {
static forRoot(): DynamicModule {
return {
module: MyModule,
providers: [
{
provide: 'ASYNC_CONTEXT',
useClass: MyService,
},
],
exports: ['ASYNC_CONTEXT'],
};
}
}
This way, you’d be able to inject ‘ASYNC_CONTEXT’ as needed across your application.
Conclusion
Async local storage is a powerful feature that helps maintain a coherent state across asynchronous calls in NestJS applications. By following this guide, you’ve learned how to set up, use and integrate async local storage into your NestJS projects. Remember to use this feature judiciously to avoid memory leaks and ensure context-correct operations.