I'm using NestJS with class-validator to validate incoming requests. In my SignUpDto class, I have applied validation decorators like IsNotEmpty, IsString, and IsEmail to validate the email and password fields. I want the validation error to be thrown in the same order as the decorators are applied but throw only one the erorrs (if ).
To achieve this, i am using exceptionFactory to throw the only one error in the app.useGlobalPipes configuration. However, the errors are not thrown in the expected order(bacause i am returning only the error at index 0)
i there any way solution so i can thow the error in the samee order but one error at a time?
// SignUpDto.ts
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
export class SignUpDto {
@IsNotEmpty({ message: 'Email should not be empty' })
@IsString({ message: 'Invalid email format' })
@IsEmail({}, { message: 'Invalid email format' })
email: string;
@IsNotEmpty({ message: 'Password should not be empty' })
@IsString({ message: 'Invalid password format' })
@MinLength(8, { message: 'Password must be at least 8 characters long' })
password: string;
}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {
BadRequestException,
HttpException,
HttpStatus,
ValidationPipe,
} from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: true,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
credentials: true,
});
app.useGlobalPipes(
new ValidationPipe({
stopAtFirstError: true,
whitelist: true,
exceptionFactory: (errors) => {
const messages = errors[0].constraints;
const message = Object.values(messages)[0];
const response = { message, statusCode: HttpStatus.BAD_REQUEST };
throw new HttpException(response, HttpStatus.BAD_REQUEST);
},
}),
);
await app.listen(5001);
}
bootstrap();
You are using TypeScript decorators (the ones you import from class-validator
) to add the validation for your DTOs.
TypeScript decorators are executed bottom-to-top.
When multiple decorators apply to a single declaration, their evaluation is similar to function composition in mathematics. In this model, when composing functions f and g, the resulting composite (f ∘ g)(x) is equivalent to f(g(x)).
As such, the following steps are performed when evaluating multiple decorators on a single declaration in TypeScript:
- The expressions for each decorator are evaluated top-to-bottom.
- The results are then called as functions from bottom-to-top.
https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-composition
That means in your given DTO:
export class SignUpDto {
@IsNotEmpty({ message: 'Email should not be empty' })
@IsString({ message: 'Invalid email format' })
@IsEmail({}, { message: 'Invalid email format' })
email: string;
}
The execution order of your decorators will be:
IsEmail
IsString
IsNotEmpty
I'm assuming you want it the other way. That means, you need to adjust the decorator order to:
export class SignUpDto {
@IsEmail({}, { message: 'Invalid email format' })
@IsString({ message: 'Invalid email format' })
@IsNotEmpty({ message: 'Email should not be empty' })
email: string;
}
This should fix the order for you.
For your second query of returning only 1 error, your approach looks good. Using both stopAtFirstError
and executionFactory
will be the easiest way.