Search code examples
expressnestjsfastify

How to get handler route in NestJS Interceptor (For both Express and Fastify)


I am having issues trying to get a hold of the NestJS handler's route in an interceptor I am writing. For instance, if a Controller had a route as such:

  @Get('/params/:p1/:p2')
  routeWithParams(@Param() params): string {
    return `params are ${params.p1} and ${params.p2}`;
  }

I would like the ability to grab the value /param/:p1/:p2 programatically. Using the url and deparameterizing is NOT an option, as there is not really a way to do so in a %100 airtight manner. Did some digging and have not found a documented way to grab the route for the handler. Wondering if anyone else has had luck? Here is some example code I stripped down from my project:

import { Injectable, ExecutionContext, CallHandler, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Request } from 'express';
import { FastifyRequest } from 'fastify';

function isExpressRequest(request: Request | FastifyRequest): request is Request {
  return (request as FastifyRequest).req === undefined;
}

@Injectable()
export class MyInterceptor implements NestInterceptor {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request: Request | FastifyRequest = context.switchToHttp().getRequest();

    if( !isExpressRequest(request) ) { // if req fufills the FastifyRequest interface, we will rename the transaction
      const req = request as FastifyRequest;
      const route = `` // TODO how can I grab the route either using the FastifyRequest or ExecutionContext??
    } // otherwise, we are in express request
    const route = `` // TODO how can I grab the route either using the Request or ExecutionContext?

    return next.handle();
  }
}

If it turns out that an interceptor won't do the trick and something else like a Guard could work to grab this information I'm all ears.


Solution

  • After talking to the good folks on the NestJS Discord, I was pointed towards Reflectors. So, using a reflector I can actually fetch the path data passed into the HTTP method decorator.

    import { Injectable, ExecutionContext, CallHandler, NestInterceptor } from '@nestjs/common';
    import { Reflector } from '@nestjs/core';
    import { Observable } from 'rxjs';
    import { Request } from 'express';
    import { FastifyRequest } from 'fastify';
    import { PATH_METADATA } from '@nestjs/common/constants';
    
    function isExpressRequest(request: Request | FastifyRequest): request is Request {
      return (request as FastifyRequest).req === undefined;
    }
    
    @Injectable()
    export class MyInterceptor implements NestInterceptor {
      constructor(private readonly reflector: Reflector) {}
    
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const request: Request | FastifyRequest = context.switchToHttp().getRequest();
    
        const path = this.reflector.get<string[]>(PATH_METADATA, context.getHandler()); 
        const method = isExpressRequest(request) ? request.method : (request as FastifyRequest).req.method;
    
        // can now do something with the path and method
    
        return next.handle();
      }
    }
    
    

    Now there is the valid concern that the PATH_METADATA key could move in NestJS common, breaking this code. Totally possible and something to look out for. But the fact that according to the git blame for the constants, the path key has not been updated for 3 years mollifies those concerns imo: https://github.com/nestjs/nest/blame/master/packages/common/constants.ts