8

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.

SomeAnonGuy
  • 256
  • 2
  • 6

2 Answers2

14

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

Remi Guan
  • 21,506
  • 17
  • 64
  • 87
SomeAnonGuy
  • 256
  • 2
  • 6
  • 2
    I found that this approach wouldn't quite work if you wanted to get the full path (including controller level path and global prefix). For that, I resorted to using `const path = context.switchToHttp().getRequest().route?.path`, which seems to work fine. – Thomas Ebert Mar 08 '21 at 10:21
1

To retrieve the request full path without directly using the Request, you can make use of Reflector and the ApplicationConfig injectable in the following way :

import { Injectable, ExecutionContext, CallHandler, NestInterceptor } from '@nestjs/common';
import { ApplicationConfig, Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { PATH_METADATA } from '@nestjs/common/constants';
import * as path from 'path';

@Injectable()
export class MyInterceptor implements NestInterceptor {
  constructor(
    private readonly reflector: Reflector,
    private readonly appConfig: ApplicationConfig
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {

    const globalPath = this.appConfig.getGlobalPrefix();
    const controllerPath = this.reflector.get<string>(PATH_METADATA, context.getClass());
    const routeHandlerPath = this.reflector.get<string>(PATH_METADATA, context.getHandler());
    
    const path = path.join(globalPath, controllerPath, routeHandlerPath)
    
    // Do something with path

    return next.handle();
  }
}

zoncrd
  • 11
  • 3