Skip to content

Instantly share code, notes, and snippets.

@josephdpurcell
Last active July 8, 2024 09:36
Show Gist options
  • Save josephdpurcell/9af97c36148673de596ecaa7e5eb6a0a to your computer and use it in GitHub Desktop.
Save josephdpurcell/9af97c36148673de596ecaa7e5eb6a0a to your computer and use it in GitHub Desktop.
NestJS: Ignore global ValidationPipe interceptor for a controller route

What is this?

This is an example of how to ignore a global validation pipe for a specific parameter, e.g. a request body. In fact, this example just shows a request body but you could apply this principle to other decorators.

This approach assumes validateCustomDecorators: false in the global validation pipe. If validateCustomDecorators is true in the global pipe I think you're out of luck. If that is your situation, consider refactoring so that validateCustomDecorators is false in the global pipe and then have each custom decorator add validation if it needs it.

How does this work?

The NestJS ValidationPipe does not validate custom decorators. So, in this above example we just make a @RawBody() param decorator, and NestJS will skip validating it.

Take a look at NestJS' ValidationPipe class at the relevant points:

Here is where it checks to see if the decorator type is 'custom':

https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L164-L166

Here is where it returns if validation shouldn't apply:

https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L102-L106

Are there alternative approaches?

Alternate 1: Use @ValidBody with custom overrides to the global ValidationPipe

You could just change the behavior of the global validation pipe. This is a bit tricky and it only works on the param decorator level. First, you need a custom decorator:

export const RawBody = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): any => {
    const request = ctx.switchToHttp().getRequest();
    return request.body;
  }
);

Then, you need to build another custom decorator that composes that with a validation pipe like so:

export const ValidBody = () =>
  RawBody(
    new ValidationPipe({
      validateCustomDecorators: true,
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    })
  );

Note the use of validateCustomDecorators: true here! This is important. The validation pipe will not work unless this is true here. (But, remember, validateCustomDecorators should be false in the global validation pipe.)

Then, use @ValidBody instead of @Body wherever you want to override the global validation pipe with your custom validation pipe rules. This approach will work even if there is a global validation pipe.

Alternate 2: Use @ValidBody instead of global ValidationPipe

Instead of applying a global validator, write a custom @Body decorator (e.g. @ValidBody) and have that decorator just use a validation pipe; see https://stackoverflow.com/a/56748392/990642. Note: I think validateCustomDecorators: true would need to be enabled.

Here is an example of the decorator:

export const ValidBody = () =>
  Body(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    })
  );

Then, use @ValidBody instead of @Body. This approach does not work if there is a global validation pipe.

Alternate 3: Custom ValidationPipe

Write your own custom ValidationPipe class, use that globally, and do some checking logic there?

Links

Here are some links that talk about this issue and some documentation links that are relevant:

import { ValidationPipe } from '@nestjs/common';
// ...
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
// ...
import { Body, Controller, Post } from '@nestjs/common';
import { RawBody } from './raw-body.ts';
import { MyDto } from './my.dto';
@Controller()
export class MyController {
@Post()
async post(
@RawBody() params: MyDto
): Promise<void> {
// params will be a raw un-typed object with no validation having been run on it!
}
}
import { IsNotEmpty } from "class-validator";
export class MyDto {
@IsNotEmpty()
field: string;
}
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const RawBody = createParamDecorator(
(data: unknown, ctx: ExecutionContext): any => {
const request = ctx.switchToHttp().getRequest();
return request.body;
}
);
@Waer1
Copy link

Waer1 commented Jul 8, 2024

great work mate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment