Каждый раз, когда ваш сервис получает данные от клиента, возникает вопрос: «А вдруг там придёт мусор?». Строка вместо числа, отрицательная цена, пустое имя — всё это нужно отсечь до того, как данные попадут в логику. Pipe в NestJS — это именно та точка, где это происходит.
Что такое pipe и зачем он нужен
Раньше проверку данных писали прямо в теле метода:
@Post()
create(@Body() body: any) {
if (!body.name || typeof body.price !== 'number' || body.price < 1) {
throw new BadRequestException('Неверные данные');
}
// ... основная логика
}
С ростом числа полей это превращается в набор проверок, которые повторяются в каждом методе. Нарушение и хорошее описание ошибки — одно, а бизнес-логика — другое.
Pipe — это шаг перед методом контроллера. NestJS пропускает входные данные через pipe: тот либо возвращает их (возможно, преобразованными), либо бросает исключение — и тогда до метода запрос вообще не дойдёт.
Простейший встроенный pipe — ParseIntPipe. Параметр в пути всегда приходит строкой; pipe превращает её в число:
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
@Controller('products')
export class ProductController {
@Get(':id')
async getOne(@Param('id', ParseIntPipe) id: number) {
// id — уже число, не строка
}
}
Если в пути вместо числа окажется abc, ParseIntPipe вернёт 400 Bad Request сразу, без входа в метод.
NestJS поставляется с несколькими готовыми pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, ParseArrayPipe, DefaultValuePipe. Они закрывают частые случаи с параметрами маршрута и строки запроса.
ValidationPipe и DTO
Для проверки тела запроса нужен более мощный инструмент — ValidationPipe. Он работает в паре с двумя библиотеками:
- class-validator — декораторы-правила для полей класса (
@IsString,@IsInt,@Min,@MaxLengthи ещё несколько десятков). - class-transformer — превращает «голый» JSON-объект в экземпляр класса, чтобы декораторы сработали.
Сначала установите их:
npm install class-validator class-transformer
Затем включите ValidationPipe глобально при старте приложения:
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({ whitelist: true, transform: true }),
);
await app.listen(3000);
}
Теперь создайте DTO-класс с правилами:
import { IsString, IsInt, Min, IsOptional, MaxLength } from 'class-validator';
export class CreateProductDto {
@IsString()
@MaxLength(200)
name: string;
@IsInt()
@Min(1)
price: number;
@IsOptional()
@IsString()
category?: string;
}
И используйте его в контроллере:
@Post()
create(@Body() body: CreateProductDto) {
// body уже проверен и является экземпляром CreateProductDto
}
Если клиент пришлёт price: -5 или не передаст name, NestJS автоматически ответит 400 с подробным описанием: какие поля не прошли проверку и почему.
Что делают whitelist и transform
Два параметра при создании ValidationPipe особенно важны.
whitelist: true — срезает из тела запроса все поля, которых нет в DTO. Если клиент передаст { name: "Кружка", price: 100, role: "admin" }, поле role просто исчезнет. Это не ошибка — NestJS молча отбрасывает лишнее. Благодаря этому нельзя случайно «протащить» неожиданное поле в логику.
Если хотите, чтобы при наличии лишних полей запрос сразу отклонялся ошибкой — добавьте forbidNonWhitelisted: true:
new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })
transform: true — включает class-transformer. Без него тело запроса остаётся обычным объектом (Object), и TypeScript-тип CreateProductDto — только подсказка. С transform: true NestJS реально создаёт экземпляр класса, поэтому методы DTO (если они есть) работают, и instanceof CreateProductDto вернёт true.
Кроме того, transform: true пытается привести примитивные типы. Например, если в строке запроса пришло ?page=2 (строка), а DTO объявляет @IsInt() page: number, class-transformer преобразует строку в число автоматически.
Как написать свой pipe
Встроенных pipes хватает для большинства случаев. Если нужно нестандартное преобразование — напишите свой, реализовав интерфейс PipeTransform:
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class TrimPipe implements PipeTransform {
transform(value: unknown): unknown {
if (typeof value === 'string') {
return value.trim();
}
return value;
}
}
Применить его можно точечно — к одному параметру:
@Post()
create(@Body('name', TrimPipe) name: string) { ... }
Если pipe бросает исключение — NestJS останавливает запрос и возвращает соответствующий HTTP-ответ. Если возвращает значение — оно идёт дальше в метод.
Формат и бизнес-правило — два разных уровня
Важно понимать, за что отвечает pipe, а за что — нет.
Pipe и DTO проверяют формат: правильной ли формы данные? Строка ли имя, число ли цена, не слишком ли длинное поле? Это вопросы структуры запроса, и они решаются на границе до контроллера.
Бизнес-правила — это другое: можно ли создать продукт с таким именем, если такое имя уже занято? Достаточно ли остатков на складе? Это вопросы состояния системы, и их проверяют уже внутри — в слое логики, а не в DTO.
Смешивать уровни — частая ошибка. Когда в DTO-валидатор добавляют обращение к базе данных, граница размывается: DTO начинает знать о состоянии системы, а это делает его сложным и трудно тестируемым. Правило простое: DTO отвечает на «правильно ли выглядят данные?», а не на «допустима ли эта операция?».
Коротко
- Pipe — шаг перед методом контроллера: преобразует или проверяет входные данные, может отклонить запрос до входа в метод.
- Встроенные pipes (
ParseIntPipe,ParseBoolPipe,ParseUUIDPipeи другие) закрывают типовые случаи с параметрами маршрута. ValidationPipe+ class-validator + class-transformer — стандартный способ валидировать тело запроса через DTO-класс с декораторами.whitelist: trueотбрасывает поля, которых нет в DTO;transform: trueделает из JSON реальный экземпляр класса и приводит примитивные типы.- Свой pipe пишется через
PipeTransformи применяется точечно к параметру или методу. - Pipe отвечает за формат данных; бизнес-правила — за допустимость операции. Смешивать не стоит.
Что почитать дальше
- Контроллеры и роутинг — как NestJS сопоставляет маршруты методам.
- Exception filters — как перевести исключения pipe и контроллера в HTTP-ответы.
- Guards: аутентификация и авторизация — следующий рубеж после валидации.