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.
Fastify provides a good alternative framework for Nest because it solves design issues in a similar manner to Express. However, fastify is much faster than Express, achieving almost two times better benchmarks results. A fair question is why does Nest use Express as the default HTTP provider?
In order to set up the interceptor, we use the @UseInterceptors() decorator imported from the @nestjs/common package. Like pipes and guards, interceptors can be controller-scoped, method-scoped, or global-scoped. Hint The @UseInterceptors() decorator is imported from the @nestjs/common package.
A NestJS Interceptor is basically a class annotated with the @Injectable() decorator. If you wish to know more about @Injectable() decorator, please refer to the detailed post on NestJS Providers. However, apart from the decorator, an interceptor should also implement the NestInterceptor interface.
Finally, as we already mentioned, NestJS uses the Express framework by default as the request processing pipeline. This means if you are already familiar with Express processing, you'll be able to adapt your Express middleware to use within Nest.
We can bind interceptors on various levels such as method, controller or even global. To bind an interceptor, we use the @UseInterceptor () decorator. Basically, the LoggingInterceptor will be applicable to this route handler in the controller. If you wish to know more about controllers, refer to the detailed post about NestJS Controllers.
The simplest way to implement this feature would instead be using Nest.js Interceptors. If you come from the express world, you will be familiar with the idea of middleware. A middleware is essentially a function that receives a request and returns a response based on that request.
We use NestJs interceptors and guards daily, and most often we do not need to wrap them into factories, but every once in a while we need to pass an argument statically to them when we decorate some controller or some controller’s method. Let’s say that we need to send some metrics, KPI, to track usage of our app.
By using RxJs Observables, Interceptors allow you to inject logic into any point of the request life cycle. For a transaction interceptor, this is perfect because we need to both set up the transaction before the request is handled, and either commit it or roll it back depending on the success of the request.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With