You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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, RouterExplorer is 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:38:25