Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Validation: ValidationPipe and class-validator

Validation means checking that the client sent correct data: the title is present, it is a string, its length is within limits. In NestJS the rules are written as decorators right in the DTO, and the built-in ValidationPipe performs the check. On failure the client gets a 400 response with a list of problems, and the controller method is never called.

Installation

npm i class-validator class-transformer

class-validator provides the rule decorators; class-transformer turns plain JSON into an instance of the DTO class so that the rules can be checked.

Rules in the DTO

// src/posts/dto/create-post.dto.ts
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
 
export class CreatePostDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(120)
  title: string;
 
  @IsString()
  @IsNotEmpty()
  content: string;
}

Each field has rules: a non-empty string, and the title must also be at most 120 characters long.

Enabling ValidationPipe

The pipe is enabled for the whole application in src/main.ts:

// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Now every @Body() argument whose type is a class with rules is checked before the method is called. Send a post without a title:

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"content":"Text without a title"}'

The server answers with code 400 and a list of broken rules:

{
  "message": [
    "title must be shorter than or equal to 120 characters",
    "title should not be empty",
    "title must be a string"
  ],
  "error": "Bad Request",
  "statusCode": 400
}

A custom error text goes into the last argument of a rule: @IsNotEmpty({ message: 'Enter a title' }).

Common rules

DecoratorWhat it checks
@IsString()A string
@IsInt()An integer
@IsEmail()An email address
@IsNotEmpty()Not an empty string, not null and not undefined
@MinLength(8)A string at least 8 characters long
@MaxLength(120)A string at most 120 characters long
@IsOptional()The field may be omitted
@IsIn(['draft', 'published'])One of the listed values

Pipe options

OptionWhat it does
whitelist: trueRemoves body fields that are not in the DTO
forbidNonWhitelisted: trueResponds with 400 instead of removing them
transform: truePasses a DTO class instance to the method and converts parameter types

whitelist protects against extra fields: if the client sends "role":"admin", that field never reaches the service.

Common mistake

Rules do not fire if the argument type is an interface or any: the pipe has nothing to compare the data with. The same happens if you forget app.useGlobalPipes(...) — a request with an empty body quietly reaches the service. Test validation with a deliberately bad request right after enabling it.

Thanks for reading!

Donate to the site — we work hard for you.