Пошук уроків, статей та іншого контенту
Налаштуєте декларативні правила валідації DTO та перетворення вхідних plain-об’єктів на екземпляри класів.
class-validator і class-transformerУ NestJS DTO описує форму даних, які надходять у контролер. Але сам TypeScript не перевіряє значення під час виконання програми:
export class CreateUserDto {
email: string;
age: number;
}Після компіляції типи string і number не захищають застосунок від такого запиту:
{
"email": "not-an-email",
"age": "unknown",
"role": "admin"
}Для перевірки вхідних даних у NestJS зазвичай використовують:
class-validator — декларативні правила валідації;
class-transformer — перетворення plain-об’єктів на екземпляри класів і перетворення значень.
Встановіть пакети:
npm install class-validator class-transformerУ NestJS валідацію зазвичай вмикають глобально за допомогою ValidationPipe.
Правила додаються до властивостей DTO за допомогою декораторів:
import { IsEmail, IsInt, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(2)
name: string;
@IsInt()
age: number;
}Тепер class-validator перевірятиме:
email — чи має значення формат електронної пошти;
name — чи є рядком довжиною щонайменше два символи;
age — чи є цілим числом.
Декоратори не змінюють значення самі по собі. Вони лише описують правила, які потрібно виконати.
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
return {
email: dto.email,
name: dto.name,
age: dto.age,
};
}
}DTO використовується як тип для @Body(). Реальна перевірка відбудеться лише після підключення ValidationPipe.
ValidationPipeУ 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({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}),
);
await app.listen(3000);
}
bootstrap();transformtransform: trueNestJS перетворює вхідний plain-об’єкт на екземпляр DTO-класу.
Це важливо не лише для синтаксису. Після перетворення екземпляр DTO має прототип класу та може коректно працювати з метаданими, які використовують декоратори.
Також transform може перетворювати значення відповідно до типу властивості. Для передбачуваної поведінки краще явно вказувати типи через @Type.
whitelistwhitelist: trueВидаляє з об’єкта властивості, які не мають жодного валідатора в DTO.
Наприклад, якщо DTO описує лише email, name і age, поле role буде видалене.
forbidNonWhitelistedforbidNonWhitelisted: trueЗамість тихого видалення невідомих властивостей NestJS повертає помилку.
Це корисно для API, де випадкові або зайві поля не повинні непомітно ігноруватися.
class-transformerДані з HTTP-запиту зазвичай надходять як plain-об’єкт. Наприклад, параметри URL і query-параметри часто мають рядковий тип:
GET /users?limit=10Навіть якщо значення виглядає як число, на рівні HTTP воно надходить як рядок "10".
Для явного перетворення використовуйте @Type:
import { Type } from 'class-transformer';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class FindUsersDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number;
}Контролер:
import { Controller, Get, Query } from '@nestjs/common';
import { FindUsersDto } from './find-users.dto';
@Controller('users')
export class UsersController {
@Get()
findAll(@Query() query: FindUsersDto) {
return {
limit: query.limit ?? 20,
limitType: typeof query.limit,
};
}
}Для запиту:
GET /users?limit=10значення query.limit буде числом 10, а не рядком "10".
Якщо передати:
GET /users?limit=abcперетворення дасть некоректне числове значення, і @IsInt() відхилить запит.
DTO може містити інший DTO. Для коректного перетворення вкладеного об’єкта потрібно використовувати @ValidateNested() разом із @Type().
import { Type } from 'class-transformer';
import { IsString, ValidateNested } from 'class-validator';
export class AddressDto {
@IsString()
city: string;
@IsString()
street: string;
}
export class CreateUserDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}Без @ValidateNested() правила AddressDto не будуть застосовані.
Без @Type(() => AddressDto) class-transformer не знатиме, екземпляром якого класу має стати address.
Очікуване тіло запиту:
{
"name": "Олена",
"address": {
"city": "Львів",
"street": "Городоцька"
}
}Для необов’язкового поля використовуйте @IsOptional():
import { IsEmail, IsOptional, IsString, MinLength } from 'class-validator';
export class UpdateUserDto {
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsString()
@MinLength(2)
name?: string;
}@IsOptional() пропускає подальші перевірки, якщо значення дорівнює null або undefined.
Він не означає, що будь-яке передане значення буде прийнято. Якщо email передати як неприпустимий рядок, @IsEmail() все одно поверне помилку.
Нижче наведено мінімальний приклад контролера, DTO та налаштування застосунку.
create-user.dto.tsimport { Type } from 'class-transformer';
import {
IsEmail,
IsInt,
IsString,
Min,
MinLength,
ValidateNested,
} from 'class-validator';
export class AddressDto {
@IsString()
@MinLength(2)
city: string;
@IsString()
@MinLength(3)
street: string;
}
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(2)
name: string;
@Type(() => Number)
@IsInt()
@Min(18)
age: number;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}users.controller.tsimport { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
return {
message: 'Користувача створено',
user: {
email: dto.email,
name: dto.name,
age: dto.age,
ageType: typeof dto.age,
address: dto.address,
},
};
}
}main.tsimport { 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({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}),
);
await app.listen(3000);
}
bootstrap();Запит із коректними даними:
POST /users
Content-Type: application/json{
"email": "olena@example.com",
"name": "Олена",
"age": "25",
"address": {
"city": "Львів",
"street": "Городоцька"
}
}Завдяки @Type(() => Number) значення age у DTO буде числом 25.
Запит із помилками:
{
"email": "invalid",
"name": "О",
"age": "abc",
"address": {
"city": "",
"street": "A"
},
"role": "admin"
}буде відхилено через:
неправильний формат email;
недостатню довжину name;
некоректне ціле число age;
помилки у властивостях address;
невідому властивість role.
ValidationPipe автоматично поверне клієнту HTTP-відповідь зі статусом 400 Bad Request.
class-validatorДля рядків:
@IsString()
@MinLength(3)
@MaxLength(100)
@IsNotEmpty()Для чисел:
@IsInt()
@IsNumber()
@Min(0)
@Max(100)Для спеціальних значень:
@IsEmail()
@IsUrl()
@IsUUID()
@IsBoolean()
@IsDateString()Для масивів:
import { IsArray, IsString } from 'class-validator';
export class CreatePostDto {
@IsArray()
@IsString({ each: true })
tags: string[];
}Параметр { each: true } застосовує перевірку до кожного елемента масиву.
ValidationPipeДекоратори DTO не виконуються автоматично. Якщо pipe не підключений, запит може пройти без перевірки.
app.useGlobalPipes(new ValidationPipe());transform: trueБез transform: true значення з query-параметрів і тіла запиту можуть залишатися plain-об’єктами або рядками.
Для явного перетворення чисел використовуйте:
@Type(() => Number)@ValidateNested() без @Type()Такої комбінації недостатньо:
@ValidateNested()
address: AddressDto;Потрібно додати:
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;Тип:
age: number;не гарантує, що клієнт справді надіслав число. Runtime-валідацію виконують декоратори class-validator.
enableImplicitConversionМожна ввімкнути неявне перетворення:
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
});Але явний @Type(() => Number) зазвичай зрозуміліший і передбачуваніший. Він показує безпосередньо в DTO, як саме має бути перетворене конкретне поле.
@IsOptional() для полів оновленняЯкщо поле не є обов’язковим, але має інші валідатори, без @IsOptional() порожнє значення може спричинити помилку.
export class UpdateUserDto {
@IsOptional()
@IsEmail()
email?: string;
}class-validator описує правила перевірки DTO за допомогою декораторів.
class-transformer перетворює plain-об’єкти на екземпляри DTO-класів.
Глобальний ValidationPipe вмикає обробку вхідних даних у NestJS.
transform: true потрібен для перетворення значень і DTO.
@Type(() => Number) явно перетворює рядок на число.
@ValidateNested() і @Type() разом забезпечують перевірку вкладених DTO.
whitelist видаляє невідомі властивості, а forbidNonWhitelisted відхиляє такі запити.
@IsOptional() позначає поле як необов’язкове, не вимикаючи його перевірку, якщо значення передано.