Home > Software design >  NestJS Interceptor not enriching Exception handlers
NestJS Interceptor not enriching Exception handlers

Time:04-21

I am using both a Exception Filter, Interceptor and Validation Pipe. All of these work as expected on their own. I set these globally, as that is all I need at this time. The AllExceptionsFilter enriches the default exception from NestJS to include the URL, Http method, etc. The AddProgramVersionToResponseHeaderInterceptor is used to add a custom header to the response, which contains my application and version. The AppGlobalValidationPipeOptions is used to convert the Http Status code from 400 on a validation error, to be 422 Unprocessable Entity.

main.ts

...
const AppHttpAdapter = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(AppHttpAdapter));
app.useGlobalInterceptors(new AddProgramVersionToResponseHeaderInterceptor());
app.useGlobalPipes(new ValidationPipe(AppGlobalValidationPipeOptions)); 
await app.listen(IpPort);

All of these work fine, until I get an exception in the code, such as a bad route.
GET /doesnotexist will return the enriched 404 result, which has what I want, but the Http Header field from the AddProgramVersionToResponseHeaderInterceptor does not update the header.

I have defined the global settings in different orders to see if that would allow the interceptor to add the Http header, but that did not work.

AddProgramVersionToResponseHeaderInterceptor

export class AddProgramVersionToResponseHeaderInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const AppInfo: ApplicationInformationService = new ApplicationInformationService();
        const EmulatorInfo: string = `${AppInfo.Name}/${AppInfo.Version}`;

        const ResponseObj: ExpressResponse = context.switchToHttp().getResponse();
        ResponseObj.setHeader('my-custom-header', EmulatorInfo);

        return next.handle().pipe();
    }
}

AllExceptionsFilter

export class AllExceptionsFilter implements ExceptionFilter {
    constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

    catch(exception: unknown, host: ArgumentsHost): void {
        // In certain situations `httpAdapter` might not be available in the
        // constructor method, thus we should resolve it here.
        const { httpAdapter } = this.httpAdapterHost;

        const ctx = host.switchToHttp();
        const HttpStatusCode: number = exception instanceof HttpException ? exception.getStatus() : HttpStatus.EXPECTATION_FAILED;
        const RequestData: string = httpAdapter.getRequestUrl(ctx.getRequest());
        const RequestURLInfo: string[] = RequestData.split('?');

        const ResponseBody: IBackendException = {
            Hostname: httpAdapter.getRequestHostname(ctx.getRequest()),
            Message: exception instanceof HttpException ? (exception.getResponse() as INestHttpException).message : [(exception as Error).message.toString()],
            Method: httpAdapter.getRequestMethod(ctx.getRequest()),
            StackTrace: exception instanceof HttpException ? '' : (exception as Error).stack,
            StatusCode: HttpStatusCode,
            Timestamp: new Date().toISOString(),
            URL: RequestURLInfo[0],
            Parameters: RequestURLInfo[1],
        };
        httpAdapter.reply(ctx.getResponse(), ResponseBody, HttpStatusCode);
    };

AppGlobalValidationPipeOptions

export const AppGlobalValidationPipeOptions: ValidationPipeOptions = {
    errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
    transform: true,
    whitelist: true,
};

CodePudding user response:

Interceptors bind to route handlers (class methods) that are marked with an HTTP verb decorator. If there is no route handler (like a 404) then then interceptor cannot be called. I have the same problem with my OgmaInterceptor. You can either make an @All('*') handler that throws a NotFoundException or just take care of it in the filter as you already are.

  • Related