NestJS技术问询:如何在中间件中获取ExecutionContext
Great question! In NestJS, middlewares don't have direct access to ExecutionContext like guards or interceptors do—and that's intentional. Middlewares sit closer to the underlying Express/Koa layer, while ExecutionContext is a higher-level abstraction designed to work with route handlers and their metadata. But don't worry, there are a few workarounds to get the metadata you need in a middleware.
方案1:通过RouterExplorer手动匹配路由获取元数据
Nest's internal RouterExplorer can help you fetch all registered routes, and you can match the incoming request to the corresponding route handler to extract metadata. Here's how to implement it:
import { Injectable, NestMiddleware, RouterExplorer } from '@nestjs/core'; import { Request, Response, NextFunction } from 'express'; import { SOME_KEY } from './your-metadata.constants'; @Injectable() export class SomeMiddleware implements NestMiddleware { constructor(private readonly routerExplorer: RouterExplorer) {} use(req: Request, res: Response, next: NextFunction) { // Extract request method and path const reqMethod = req.method.toLowerCase(); const reqPath = req.path; // Fetch all registered routes and find the matching one const allRoutes = this.routerExplorer.getRoutes(); const matchedRoute = allRoutes.find(route => { // Note: This is a basic match—you may need to handle dynamic routes (like /users/:id) // or wildcards with additional logic return route.method === reqMethod && route.path === reqPath; }); if (matchedRoute) { // Retrieve metadata from the matched handler const handlerMetadata = Reflect.getMetadata(SOME_KEY, matchedRoute.handler); console.log('Fetched metadata:', handlerMetadata); // You can attach this metadata to the request object for later use (req as any).customMetadata = handlerMetadata; } next(); } }
Pros & Cons:
- Pros: Directly leverages Nest's internal routing system, no extra services needed.
- Cons: Route matching logic can get complex for dynamic routes or wildcard paths. Also,
RouterExploreris an internal API, so there's a small risk of breaking changes in future Nest versions.
方案2:预收集元数据到全局存储服务
Another approach is to pre-collect all route metadata during app initialization and store it in a service, then inject that service into your middleware to look up metadata by request details.
Step 1: Create a metadata storage service
import { Injectable } from '@nestjs/common'; @Injectable() export class RouteMetadataStorage { private readonly metadataMap = new Map<string, any>(); // Register metadata using a key like "method:path" registerMetadata(method: string, path: string, metadata: any): void { const key = `${method.toLowerCase()}:${path}`; this.metadataMap.set(key, metadata); } // Fetch metadata by request method and path getMetadata(method: string, path: string): any { const key = `${method.toLowerCase()}:${path}`; return this.metadataMap.get(key); } }
Step 2: Update your custom decorator to track metadata
import { SetMetadata } from '@nestjs/common'; import { SOME_KEY } from './your-metadata.constants'; // Custom decorator that sets standard metadata AND marks the handler for collection export const SomeDecorator = (metadata: any) => { return (target: any, propertyKey: string) => { // Set metadata for guards/interceptors as usual SetMetadata(SOME_KEY, metadata)(target, propertyKey); // Store temporary reference on the class to collect later if (!target.__routeMetadata) { target.__routeMetadata = []; } target.__routeMetadata.push({ handlerName: propertyKey, metadata, }); }; };
Step 3: Initialize metadata during app startup
Use APP_INITIALIZER to scan all routes and populate the storage service:
import { Module, APP_INITIALIZER } from '@nestjs/common'; import { RouterExplorer } from '@nestjs/core'; import { RouteMetadataStorage } from './route-metadata.storage'; // Factory function to collect metadata on app startup const initializeMetadata = ( routerExplorer: RouterExplorer, metadataStorage: RouteMetadataStorage, ) => { return () => { const allRoutes = routerExplorer.getRoutes(); allRoutes.forEach(route => { // Fetch temporary metadata from the handler's class const classMetadata = route.handler.target.__routeMetadata; if (classMetadata) { const matchedHandler = classMetadata.find( item => item.handlerName === route.handler.name, ); if (matchedHandler) { metadataStorage.registerMetadata(route.method, route.path, matchedHandler.metadata); } } }); }; }; @Module({ providers: [ RouteMetadataStorage, { provide: APP_INITIALIZER, useFactory: initializeMetadata, inject: [RouterExplorer, RouteMetadataStorage], multi: true, }, ], }) export class AppModule {}
Step 4: Use the storage service in your middleware
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { RouteMetadataStorage } from './route-metadata.storage'; @Injectable() export class SomeMiddleware implements NestMiddleware { constructor(private readonly metadataStorage: RouteMetadataStorage) {} use(req: Request, res: Response, next: NextFunction) { const metadata = this.metadataStorage.getMetadata(req.method, req.path); if (metadata) { console.log('Fetched metadata:', metadata); (req as any).customMetadata = metadata; } next(); } }
Pros & Cons:
- Pros: Clean lookup in middleware, no complex route matching logic. Works reliably with all route types.
- Cons: Requires extra setup (storage service + initialization logic), adds a small overhead during app startup.
方案3:考虑迁移逻辑到守卫/拦截器(如果可行)
If your use case doesn't strictly require a middleware, consider moving the logic to a guard or interceptor. These components natively receive ExecutionContext, making it trivial to access handler metadata. For example:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { SOME_KEY } from './your-metadata.constants'; @Injectable() export class SomeGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const metadata = this.reflector.get(SOME_KEY, context.getHandler()); console.log('Fetched metadata:', metadata); // Your logic here return true; } }
This is often the simplest solution if your workflow fits within the guard/interceptor lifecycle.
Summary
While NestJS middlewares can't directly access ExecutionContext, you can still retrieve handler metadata using either route matching with RouterExplorer, a pre-populated metadata storage service, or by migrating logic to guards/interceptors. Choose the approach that best fits your application's complexity and requirements.
内容的提问来源于stack exchange,提问作者jueschus

