Custom validation decorators help you check if data meets your own rules. They make your code cleaner and easier to understand.
Custom validation decorators in NestJS
Introduction
Syntax
NestJS
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; @ValidatorConstraint({ async: false }) class CustomValidator implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { // Your validation logic here return true; // or false } defaultMessage(args: ValidationArguments) { return 'Custom error message'; } } export function CustomValidation(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: CustomValidator, }); }; }
Use
@ValidatorConstraint to create the validation logic class.Use
registerDecorator inside a function to create the decorator.Examples
@IsEven() that checks if a number is even.NestJS
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; @ValidatorConstraint({ async: false }) class IsEvenConstraint implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { return typeof value === 'number' && value % 2 === 0; } defaultMessage(args: ValidationArguments) { return 'Number must be even'; } } export function IsEven(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsEvenConstraint, }); }; }
@IsLongerThan('otherProperty') that checks if one string is longer than another property.NestJS
export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [property],
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return typeof value === 'string' && typeof relatedValue === 'string' && value.length > relatedValue.length;
},
defaultMessage(args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
return `${args.property} must be longer than ${relatedPropertyName}`;
},
},
});
};
}Sample Program
This program defines a custom decorator @IsPositive to check if a number is positive. It then creates two products: one with a positive price and one with a negative price. It validates both and prints the errors.
NestJS
import { validate } from 'class-validator'; import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; @ValidatorConstraint({ async: false }) class IsPositiveConstraint implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { return typeof value === 'number' && value > 0; } defaultMessage(args: ValidationArguments) { return 'Value must be a positive number'; } } function IsPositive(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsPositiveConstraint, }); }; } class Product { @IsPositive({ message: 'Price must be positive!' }) price: number; constructor(price: number) { this.price = price; } } async function run() { const product1 = new Product(100); const product2 = new Product(-5); const errors1 = await validate(product1); const errors2 = await validate(product2); console.log('Product 1 errors:', errors1.length === 0 ? 'No errors' : errors1); console.log('Product 2 errors:', errors2.length === 0 ? 'No errors' : errors2.map(e => e.constraints)); } run();
Important Notes
Custom validators can be synchronous or asynchronous by setting async: true in @ValidatorConstraint.
Always provide clear error messages in defaultMessage or via ValidationOptions.
Use constraints to pass extra data to your validator if needed.
Summary
Custom validation decorators let you create your own rules for data checks.
They keep your code clean and reusable.
Use @ValidatorConstraint and registerDecorator to build them.
Practice
1. What is the main purpose of creating a custom validation decorator in NestJS?
easy
Solution
Step 1: Understand validation decorators
Validation decorators in NestJS are used to check if data meets certain rules before processing.Step 2: Purpose of custom validation decorators
Custom validation decorators let you create your own specific rules that are not covered by built-in validators.Final Answer:
To define your own rules for validating data inputs -> Option DQuick Check:
Custom validation decorators = custom data rules [OK]
Hint: Custom decorators create your own data validation rules [OK]
Common Mistakes:
- Confusing validation decorators with routing or database features
- Thinking custom decorators generate schemas automatically
- Assuming they handle HTTP requests directly
2. Which of the following is the correct way to start defining a custom validator class in NestJS?
easy
Solution
Step 1: Identify the decorator for custom validators
In NestJS, custom validation classes must be marked with @ValidatorConstraint to tell the system they are validators.Step 2: Differentiate from other decorators
@Controller is for routing, @Injectable for services, and @Module for modules, not for validation.Final Answer:
Use @ValidatorConstraint decorator on the class -> Option CQuick Check:
@ValidatorConstraint marks custom validator classes [OK]
Hint: Custom validators use @ValidatorConstraint decorator [OK]
Common Mistakes:
- Using @Controller or @Module instead of @ValidatorConstraint
- Forgetting to decorate the validator class
- Confusing service decorators with validation decorators
3. Given this custom validator code snippet, what will be the output when validating the value "abc123"?
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
@ValidatorConstraint({ name: 'isAlphaOnly', async: false })
export class IsAlphaOnly implements ValidatorConstraintInterface {
validate(text: string) {
return /^[A-Za-z]+$/.test(text);
}
defaultMessage() {
return 'Text must contain only letters';
}
}medium
Solution
Step 1: Analyze the regex in validate method
The regex /^[A-Za-z]+$/ matches only strings with letters from start to end, no numbers allowed.Step 2: Check the test string 'abc123'
This string contains numbers '123', so it does not match the regex and validate returns false.Final Answer:
Validation fails because 'abc123' contains numbers -> Option AQuick Check:
Regex /^[A-Za-z]+$/ rejects numbers [OK]
Hint: Check regex carefully to see what characters it allows [OK]
Common Mistakes:
- Assuming partial matches pass validation
- Ignoring numbers in the test string
- Thinking regex allows digits when it does not
4. Identify the error in this custom validation decorator code:
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
@ValidatorConstraint({ async: true })
export class IsEvenNumber implements ValidatorConstraintInterface {
validate(value: number) {
return value % 2 === 0;
}
}
export function IsEven(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isEven',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsEvenNumber,
});
};
}medium
Solution
Step 1: Check async flag and validate method
The @ValidatorConstraint decorator has async: true, so validate must return a Promise<boolean>.Step 2: Validate method returns boolean, not Promise
Currently, validate returns a boolean directly, which conflicts with async: true.Final Answer:
The validate method should return a Promise because async is true -> Option AQuick Check:
async: true requires Promise return [OK]
Hint: If async: true, validate must return Promise<boolean> [OK]
Common Mistakes:
- Returning boolean instead of Promise when async is true
- Omitting @ValidatorConstraint decorator
- Misusing registerDecorator parameters
5. You want to create a custom validation decorator that checks if a string property contains at least one uppercase letter and one digit. Which approach correctly combines the concepts to implement this in NestJS?
hard
Solution
Step 1: Understand custom validation structure
Custom validators require a class decorated with @ValidatorConstraint implementing validate() method.Step 2: Use registerDecorator to link the class to a decorator function
This function applies the validator to the property in the DTO.Step 3: Implement validate() with regex checking uppercase and digit presence
This ensures the string meets both conditions in one check.Final Answer:
Create a class with @ValidatorConstraint, implement validate() using regex, then use registerDecorator in a function to apply it -> Option BQuick Check:
Custom validator class + registerDecorator function = correct approach [OK]
Hint: Combine @ValidatorConstraint class with registerDecorator function [OK]
Common Mistakes:
- Skipping the validator class and using only registerDecorator
- Trying to use services instead of decorators for validation
- Assuming built-in decorators cover combined conditions
Recommended
Ready to prove it?
Pass the test and this pattern moves unknown
10 questions ~5 mineasy hard
