如何在NestJS服务中获取Interceptor注入的数据?
When working with NestJS + GolevelUP/RabbitMQ, request scope doesn’t always behave as expected because RabbitMQ message contexts aren’t tied to Nest’s default HTTP request scope. Here are two reliable ways to share your interceptor-computed data across services:
1. Use AsyncLocalStorage (ALS) for Context Propagation
ALS is a Node.js feature that preserves context across async operations, making it ideal for sharing data between interceptors and services without scope changes.
Step 1: Create an ALS Context Service
This service wraps AsyncLocalStorage to manage your transaction data:
import { Injectable } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; @Injectable() export class MessageContextService { private readonly als = new AsyncLocalStorage<Map<string, any>>(); // Run a callback within the context runWithContext<T>(contextData: Map<string, any>, callback: () => T): T { return this.als.run(contextData, callback); } // Retrieve data from context get(key: string): any { const store = this.als.getStore(); return store?.get(key); } // Add data to context set(key: string, value: any): void { const store = this.als.getStore(); if (store) store.set(key, value); } }
Step 2: Update Your Interceptor to Use ALS
Populate the context before executing the message handler:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { Observable } from 'rxjs'; import { MessageContextService } from './message-context.service'; @Injectable() export class RabbitTransactionInterceptor implements NestInterceptor { constructor(private readonly msgContext: MessageContextService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { // Extract RabbitMQ context and compute your transaction data const rabbitCtx = context.switchToRpc().getContext(); const transactionData = { /* your computed data here */ }; // Store data in ALS context const contextMap = new Map<string, any>(); contextMap.set('transactionData', transactionData); // Execute handler within the context return this.msgContext.runWithContext(contextMap, () => next.handle()); } }
Step 3: Access Data in Your Services
Inject the MessageContextService to retrieve the data:
import { Injectable } from '@nestjs/common'; import { MessageContextService } from './message-context.service'; @Injectable() export class ServiceInjected1 { constructor(private readonly msgContext: MessageContextService) {} someCall(): void { const transactionData = this.msgContext.get('transactionData'); // Use the data as needed } }
2. Request-Scoped Context Provider (Nest-Native Approach)
If you prefer using Nest’s built-in scoping, ensure your context is tied to each RabbitMQ message by marking relevant providers as request-scoped.
Step 1: Create a Request-Scoped Context Service
import { Injectable, Scope } from '@nestjs/common'; @Injectable({ scope: Scope.REQUEST }) export class TransactionContext { transactionData: any; // Holds your computed data }
Step 2: Populate Context in the Interceptor
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { Observable } from 'rxjs'; import { TransactionContext } from './transaction-context.service'; @Injectable() export class RabbitTransactionInterceptor implements NestInterceptor { constructor(private readonly txContext: TransactionContext) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const rabbitCtx = context.switchToRpc().getContext(); this.txContext.transactionData = { /* your computed data */ }; return next.handle(); } }
Step 3: Mark Your RabbitMQ Controller as Request-Scoped
This ensures a new TransactionContext instance is created per message:
@Controller() @Injectable({ scope: Scope.REQUEST }) export class RabbitController { constructor(private readonly serviceInjected1: ServiceInjected1) {} @MessagePattern('your-queue-name') async handleMessage(message: any) { await this.serviceInjected1.someCall(); } }
Step 4: Access Data in Services
Inject the TransactionContext directly:
import { Injectable } from '@nestjs/common'; import { TransactionContext } from './transaction-context.service'; @Injectable() export class ServiceInjected2 { constructor(private readonly txContext: TransactionContext) {} someCall(): void { const data = this.txContext.transactionData; // Use the data } }
Key Notes
- ALS Pros: Lightweight, no scope changes, avoids performance overhead of request-scoped providers.
- Request-Scoped Pros: Uses Nest's native DI system, easier to understand for those familiar with Nest scoping.
- Pitfalls: For ALS, ensure all async operations related to the message run within the
runWithContextcallback. For request-scoped, be aware of potential performance impacts with high message volumes.
内容的提问来源于stack exchange,提问作者Patrick Acioli

