Learn NestJS - Validation & Exception Handling
Episode 7 of 24

Learn NestJS - Validation & Exception Handling

This episode covers validation and error handling in NestJS: validation with class-validator and class-transformer, a global validation pipe, custom pipes, exception filters, and a consistent error response structure.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

External input can't be trusted — validation is mandatory before data reaches the business layer. On the other side, errors must be handled consistently so application clients can deal with failures properly.

Episode 7 covers both sides: validation with class-validator and class-transformer, and exception handling with pipes, filters, and a standard error response structure.

Validation with class-validator and class-transformer

Install the Packages

Install class-validator dan class-transformer
npm install class-validator class-transformer

Both are the standard libraries for validation and object transformation in the NestJS ecosystem.

Creating a DTO with Validators

A Data Transfer Object (DTO) defines the shape of the data and its validation rules:

JSDTO dengan dekorator validasi
import { IsEmail, IsString, MinLength } from "class-validator";
 
export class CreateUserDto {
  @IsString()
  @MinLength(3)
  name: string;
 
  @IsEmail()
  email: string;
}

The @IsString, @MinLength, and @IsEmail decorators are used by class-validator to validate properties when the object is converted.

Using the DTO in a Controller

JSMenggunakan DTO di controller
@Controller("users")
export class UsersController {
  @Post()
  create(@Body() createUserDto: CreateUserDto): string {
    return `User ${createUserDto.name} dibuat`;
  }
}

Without a validation pipe, the DTO only acts as a TypeScript type. To enable validation, we need the ValidationPipe.

Global Pipes and Custom Validation Pipes

Global ValidationPipe

Register ValidationPipe as a global pipe in main.ts:

JSMengaktifkan ValidationPipe global
import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
 
async function bootstrap(): Promise<void> {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );
  await app.listen(3000);
}
 
void bootstrap();

With whitelist: true, properties not present in the DTO are stripped out. forbidNonWhitelisted throws an error if foreign properties exist. transform: true turns the payload into a class instance — making the validation decorators work.

Custom Validation Pipe

For custom validation, build your own pipe:

JSCustom pipe validasi
import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from "@nestjs/common";
 
@Injectable()
export class ParseIdPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const parsed = parseInt(value, 10);
    if (Number.isNaN(parsed)) {
      throw new BadRequestException("ID harus berupa angka");
    }
    return parsed;
  }
}

A pipe is used by adding it to a parameter or using @UsePipes.

Exception Filters and HTTP Exceptions

Built-in HttpException

NestJS provides HttpException and its subclasses like BadRequestException, NotFoundException, and ConflictException:

JSMelempar NotFoundException
import { NotFoundException } from "@nestjs/common";
 
@Get(":id")
findOne(@Param("id") id: string): string {
  const user = this.usersService.findOne(id);
  if (!user) {
    throw new NotFoundException(`User ${id} tidak ditemukan`);
  }
  return user;
}

Built-in exceptions automatically produce a response with the correct status code.

Custom Exception Filter

For a consistent error format, create your own filter:

JSCustom exception filter
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common";
import { Response } from "express";
 
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();
 
    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Register this filter globally or per-controller using @UseFilters.

Standard Error Response

A Consistent Error Structure

With the filter above, all errors follow the same structure. A consistent structure makes it easy for the frontend to handle errors and simplifies debugging in production:

Struktur error response
{
  "statusCode": 404,
  "message": "User 99 tidak ditemukan",
  "timestamp": "2026-08-10T07:00:00.000Z"
}

Conclusion

Episode 7 polishes your API from both the security and developer experience sides: DTO validation with class-validator, a global ValidationPipe, custom pipes, exception filters, and consistent error responses.

Key takeaways:

  • class-validator validates DTO properties through decorators.
  • whitelist and transform make the ValidationPipe work optimally.
  • DTOs are used as the @Body type in controllers.
  • Custom pipes implement PipeTransform.
  • HttpException and its subclasses produce automatic status codes.
  • Exception filters format all errors consistently.

In the next episode 8 we'll discuss configuration and environment management — the @nestjs/config module, configuration modules, per-environment configuration, validation schemas, externalized configuration, and secret management in development and production.