Валидация: ValidationPipe и class-validator
Валидация — проверка того, что клиент прислал правильные данные: заголовок есть, это строка, длина в допустимых пределах. В NestJS правила записывают декораторами прямо в DTO, а проверку выполняет встроенный ValidationPipe. При ошибке клиент получает ответ 400 со списком проблем, и метод контроллера даже не вызывается.
Установка
npm i class-validator class-transformerclass-validator содержит декораторы правил, class-transformer превращает обычный JSON в объект класса DTO, чтобы эти правила можно было проверить.
Правила в 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;
}Каждое поле получило правила: строка, не пустая, а заголовок ещё и не длиннее 120 символов.
Включение ValidationPipe
Пайп включают для всего приложения в 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();Теперь каждый аргумент с @Body(), тип которого — класс с правилами, проверяется перед вызовом метода. Отправьте статью без заголовка:
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"content":"Текст без заголовка"}'Сервер ответит кодом 400 и списком нарушенных правил:
{
"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
}Свой текст ошибки задают в последнем аргументе правила: @IsNotEmpty({ message: 'Укажите заголовок' }).
Частые правила
| Декоратор | Что проверяет |
|---|---|
@IsString() | Строка |
@IsInt() | Целое число |
@IsEmail() | Адрес электронной почты |
@IsNotEmpty() | Не пустая строка, не null и не undefined |
@MinLength(8) | Строка не короче 8 символов |
@MaxLength(120) | Строка не длиннее 120 символов |
@IsOptional() | Поле можно не передавать |
@IsIn(['draft', 'published']) | Одно из перечисленных значений |
Настройки пайпа
| Параметр | Что делает |
|---|---|
whitelist: true | Удаляет из тела поля, которых нет в DTO |
forbidNonWhitelisted: true | Вместо удаления отвечает ошибкой 400 |
transform: true | Передаёт в метод объект класса DTO и приводит типы параметров |
whitelist защищает от лишних полей: если клиент пришлёт "role":"admin", это поле не дойдёт до сервиса.
Частая ошибка
Правила не срабатывают, если тип аргумента — интерфейс или any: пайпу не с чем сравнивать данные. То же случится, если забыть app.useGlobalPipes(...) — запрос с пустым телом спокойно дойдёт до сервиса. Проверяйте работу валидации заведомо плохим запросом сразу после её подключения.