typescript 82 lines · 3 tabs

Global ValidationPipe with class-validator DTOs and Transform in NestJS

Shared by codesnips Jul 2026
3 tabs
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
      errorHttpStatusCode: 422,
    }),
  );

  await app.listen(3000);
}

bootstrap();
3 files · typescript Explain with highlit

This snippet shows the standard NestJS approach to validating and shaping incoming HTTP payloads at the framework boundary, so controllers only ever see well-typed, sanitized data. The core idea is that request validation should be declarative and colocated with the shape it describes — the DTO — rather than scattered through imperative checks inside controllers.

In main.ts, a single app.useGlobalPipes(new ValidationPipe(...)) registers validation for every route. The whitelist: true option strips any property not decorated in the DTO, and forbidNonWhitelisted: true turns unexpected fields into a 400 instead of silently dropping them, which closes off mass-assignment style bugs. transform: true is what upgrades a plain parsed JSON object into an actual instance of the DTO class, and enableImplicitConversion lets primitive coercion (query strings to numbers, for example) happen automatically. Setting a non-default errorHttpStatusCode is a common tweak for APIs that prefer 422.

In create-user.dto.ts, the payload contract is expressed entirely through decorators. class-validator decorators like @IsEmail, @Length, @IsInt, and @Min describe the rules, while class-transformer decorators do the shaping: @Transform on email normalizes casing and trims whitespace before validation runs, and @Type(() => AddressDto) combined with @ValidateNested tells the transformer how to hydrate nested objects so the address is validated too, not treated as an opaque blob. @Expose/@Exclude semantics pair naturally with this when responses are serialized.

The order matters: transformation happens first, then validation sees the cleaned values, which is why trimming an email in @Transform prevents spurious @IsEmail failures. A subtle pitfall is that @Type is mandatory for nested DTOs — without it the nested object stays a plain Object and @ValidateNested silently passes.

In users.controller.ts, the payload arrives as a fully validated CreateUserDto instance via @Body(), with no manual parsing. Because the pipe already guaranteed the shape, create can trust its input and delegate straight to the service. This pattern trades a little decorator verbosity for controllers that stay thin, errors that are consistent across the whole app, and a validation contract that doubles as living documentation.


Related snips

Share this code

Here's the card — post it anywhere.

Global ValidationPipe with class-validator DTOs and Transform in NestJS — share card
Link copied