Overview

Validate request data with class-validator decorators. Returns detailed field-level errors.

Features

  • Detailed error formatting
  • Nested validation support
  • Whitelist enabled
  • Transform enabled

Installation

npx hanma add class-validator-pipe --framework nest

Dependencies

class-validator, class-transformer

Code

import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
  ValidationError,
} from "@nestjs/common";
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";

export interface ValidationErrorResponse {
  statusCode: 400;
  error: "Bad Request";
  message: string;
  errors: FieldError[];
}

export interface FieldError {
  field: string;
  message: string;
  constraints: string[];
}

@Injectable()
export class ClassValidatorPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }

    const object = plainToInstance(metatype, value, {
      enableImplicitConversion: true,
      excludeExtraneousValues: false,
    });

    const errors = await validate(object, {
      whitelist: true,
      forbidNonWhitelisted: true,
      forbidUnknownValues: true,
    });

    if (errors.length > 0) {
      throw new BadRequestException({
        statusCode: 400,
        error: "Bad Request",
        message: "Validation failed",
        errors: this.formatErrors(errors),
      });
    }

    return object;
  }

  private toValidate(metatype: any): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }

  private formatErrors(errors: ValidationError[]): FieldError[] {
    return errors.flatMap((error) => this.extractErrors(error));
  }

  private extractErrors(error: ValidationError, parentPath = ""): FieldError[] {
    const field = parentPath
      ? `${parentPath}.${error.property}`
      : error.property;

    const results: FieldError[] = [];

    // Handle constraints on current property
    if (error.constraints) {
      results.push({
        field,
        message: Object.values(error.constraints)[0],
        constraints: Object.values(error.constraints),
      });
    }

    // Handle nested validation errors
    if (error.children && error.children.length > 0) {
      for (const child of error.children) {
        results.push(...this.extractErrors(child, field));
      }
    }

    return results;
  }
}

// Global setup in main.ts:
/*
import { ClassValidatorPipe } from "./pipes/validation.pipe";

app.useGlobalPipes(new ClassValidatorPipe());
*/

// Example DTO with class-validator decorators:
/*
import {
  IsString,
  IsEmail,
  IsNotEmpty,
  MinLength,
  IsOptional,
  ValidateNested,
  IsArray,
} from "class-validator";
import { Type } from "class-transformer";

export class AddressDto {
  @IsString()
  @IsNotEmpty()
  street: string;

  @IsString()
  @IsNotEmpty()
  city: string;
}

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;

  @IsString()
  @IsOptional()
  name?: string;

  @ValidateNested()
  @Type(() => AddressDto)
  @IsOptional()
  address?: AddressDto;
}
*/

Usage

// Global usage
app.useGlobalPipes(new ClassValidatorPipe());

Dependencies

Frequently Asked Questions

How do I install Class Validator Pipe?
Run the following command: npx hanma add class-validator-pipe --framework nest
What dependencies are required?
This snippet requires: class-validator, class-transformer. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/pipes/validation.pipe.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.