DTO + Class-validator + Class-transformer

~25 daq · NestJS · Boshlang'ich daraja

Muammodan boshlaylik

Siz foydalanuvchi ro'yxatdan o'tish uchun endpoint yozdingiz. Frontend dan ma'lumot keladi va siz uni saqlamoqchisiz. Shoshib qolsangiz, shunday yozib qo'yishingiz mumkin:

@Post()
create(@Body() body: any) {
  return this.usersService.create(body);
}

Ishlaydi. Lekin bir kuni kimdir quyidagini yuboradi:

{
  "name": "",
  "email": "bu-email-emas",
  "age": -5,
  "role": "admin"
}

Nima bo'ladi? Siz bo'sh nom, noto'g'ri email, manfiy yosh va ruxsatsiz admin rolini ma'lumotlar bazasiga saqlab qo'yasiz. Bu — jiddiy muammo.

Aynan shu muammoni hal qilish uchun DTO va Validation kerak.


DTO nima?

DTO — Data Transfer Object. Bu — "qanday ma'lumot kelishi kerak" ni tasvirlovchi oddiy sinf.

Hayotiy misol: siz bankka borasiz. Kredit olish uchun sizdan anket to'ldirishingizni so'rashadi. Anketa — bu DTO. Unda qaysi maydonlar bo'lishi, qaysilari majburiy ekanligi aniq yozilgan. Noto'g'ri to'ldirsangiz — qabul qilishmaydi.

NestJS da DTO xuddi shunday ishlaydi.


O'rnatish

npm install class-validator class-transformer

Keyin main.ts ga global ValidationPipe qo'shamiz:

// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true, // ruxsat etilmagan maydonlarni o'chiradi
      forbidNonWhitelisted: true, // ruxsatsiz maydon kelsa xato beradi
      transform: true, // turlarni avtomatik o'zgartiradi
    }),
  );

  await app.listen(3000);
}
bootstrap();

Bu uch sozlama haqida pastda batafsil gaplashamiz.


Birinchi DTO

users papkasida dto papka yarating:

users/
├── dto/
│   ├── create-user.dto.ts
│   └── update-user.dto.ts
├── users.controller.ts
├── users.module.ts
└── users.service.ts
// users/dto/create-user.dto.ts
import {
  IsString,
  IsEmail,
  IsInt,
  IsEnum,
  MinLength,
  MaxLength,
  Min,
  Max,
  IsNotEmpty,
} from "class-validator";

export enum UserRole {
  USER = "user",
  MODERATOR = "moderator",
}

export class CreateUserDto {
  @IsNotEmpty({ message: "Ism bo'sh bo'lmasligi kerak" })
  @IsString({ message: "Ism matn bo'lishi kerak" })
  @MinLength(2, { message: "Ism kamida 2 ta harf" })
  @MaxLength(50, { message: "Ism ko'pi bilan 50 ta harf" })
  name: string;

  @IsNotEmpty({ message: "Email bo'sh bo'lmasligi kerak" })
  @IsEmail({}, { message: "Email noto'g'ri formatda" })
  email: string;

  @IsInt({ message: "Yosh butun son bo'lishi kerak" })
  @Min(18, { message: "Yosh kamida 18 bo'lishi kerak" })
  @Max(100, { message: "Yosh ko'pi bilan 100 bo'lishi kerak" })
  age: number;

  @IsEnum(UserRole, {
    message: "Rol faqat user yoki moderator bo'lishi mumkin",
  })
  role: UserRole;
}

Har bir maydon ustida decorator lar — bu validatsiya qoidalari. Agar kelgan ma'lumot bu qoidalarga uymasa — NestJS avtomatik xato qaytaradi, hatto controller ga ham yetib bormaydi.


Controller da DTO ishlatish

// users/users.controller.ts
import { Controller, Post, Body } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";
import { UsersService } from "./users.service";

@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

@Body() body: any o'rniga endi @Body() createUserDto: CreateUserDto yozdik. Shu bilan NestJS validatsiya qilishni biladi.

Endi noto'g'ri ma'lumot yuborsangiz — avtomatik bunday xato keladi:

{
  "statusCode": 400,
  "message": ["Email noto'g'ri formatda", "Yosh kamida 18 bo'lishi kerak"],
  "error": "Bad Request"
}

Siz hech narsa yozmadingiz — NestJS o'zi tekshirdi, o'zi xato qaytardi.


whitelist va forbidNonWhitelisted

main.ts da yozgan ikki sozlamani eslaymiz:

new ValidationPipe({
  whitelist: true,
  forbidNonWhitelisted: true,
});

whitelist: true — DTO da ko'rsatilmagan maydonlarni o'chiradi. Masalan, foydalanuvchi quyidagini yubborsa:

{
  "name": "Jasur",
  "email": "jasur@example.com",
  "age": 25,
  "role": "user",
  "isAdmin": true,
  "password": "12345"
}

isAdmin va password — DTO da yo'q. whitelist: true ularni avtomatik o'chiradi. Sizning kodingizga faqat name, email, age, role yetib keladi.

forbidNonWhitelisted: true — o'chirish o'rniga xato qaytaradi:

{
  "statusCode": 400,
  "message": [
    "property isAdmin should not exist",
    "property password should not exist"
  ]
}

Real loyihalarda forbidNonWhitelisted: true yaxshiroq — foydalanuvchi ortiqcha narsa yuboryaptimi, bu haqida xabar berasiz.


transform: true — avtomatik tur o'zgartirish

transform: true sozlamasi juda qulay. Misol: URL dan kelgan parametrlar har doim string. Lekin siz number kutayapsiz.

@Get(':id')
findOne(@Param('id') id: number) {  // number kutayapmiz
  return this.usersService.findOne(id);
}

transform: true bo'lmasa — id string bo'lib keladi, +id yozishga majbur bo'lardingiz. transform: true bilan — NestJS avtomatik string → number ga o'zgartiradi.


Update DTO — PartialType

Foydalanuvchini yangilayotganda barcha maydonlar majburiy emas. Faqat ismni o'zgartirmoqchi bo'lsam — faqat name yuborsam yetarli.

Buning uchun CreateUserDto ni qayta yozish shart emas. NestJS PartialType beradi:

// users/dto/update-user.dto.ts
import { PartialType } from "@nestjs/mapped-types";
import { CreateUserDto } from "./create-user.dto";

export class UpdateUserDto extends PartialType(CreateUserDto) {}

PartialType — CreateUserDto dagi barcha maydonlarni ixtiyoriy qilib beradi. Hammasi shu — bir qator. Validatsiya qoidalari saqlanadi, faqat majburiylik yo'qoladi.

npm install @nestjs/mapped-types

Class-transformer — response ni tozalash

Foydalanuvchi ma'lumotini qaytarayotganda parolni ko'rsatmaslik kerak. class-transformer ning Exclude decorator i yordam beradi:

// users/entities/user.entity.ts
import { Exclude } from "class-transformer";

export class UserEntity {
  id: number;
  name: string;
  email: string;

  @Exclude() // ← response da ko'rinmaydi
  password: string;

  constructor(partial: Partial<UserEntity>) {
    Object.assign(this, partial);
  }
}

Controller da ClassSerializerInterceptor qo'shamiz:

// users/users.controller.ts
import {
  Controller,
  Get,
  Param,
  UseInterceptors,
  ClassSerializerInterceptor,
} from "@nestjs/common";
import { UserEntity } from "./entities/user.entity";

@Controller("users")
@UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
  @Get(":id")
  findOne(@Param("id") id: number) {
    return new UserEntity({
      id: 1,
      name: "Jasur",
      email: "jasur@example.com",
      password: "yashirin_parol", // bu ko'rinmaydi
    });
  }
}

Response da foydalanuvchi faqat shuni ko'radi:

{
  "id": 1,
  "name": "Jasur",
  "email": "jasur@example.com"
}

password yo'q. Siz uni o'chirmadingiz — @Exclude() o'zi hal qildi.


Eng ko'p ishlatiladigan validatorlar

// String lar uchun
@IsString()
@IsNotEmpty()
@MinLength(2)
@MaxLength(100)
@Matches(/^[a-zA-Z]+$/, { message: 'Faqat harflar' })

// Sonlar uchun
@IsNumber()
@IsInt()
@Min(0)
@Max(100)
@IsPositive()

// Boshqalar
@IsEmail()
@IsUrl()
@IsBoolean()
@IsEnum(MyEnum)
@IsArray()
@IsDate()
@IsOptional()   // maydon ixtiyoriy, lekin kelsa — tekshiriladi

Xulosa

DTO — bu loyihangizdagi "shartnoma". Foydalanuvchi bilan qanday ma'lumot almashish kerakligi aniq belgilanadi. Bu shartnomaga uymagan ma'lumot — ichkariga kirmayin.

Eslab qoling:

  • class-validator → validatsiya qoidalari (@IsEmail, @MinLength va h.k.)
  • class-transformer → ma'lumotni o'zgartirish (@Exclude — yashirish)
  • ValidationPipe → main.ts da bir marta — hamma joyda ishlaydi
  • whitelist: true → ortiqcha maydonlar o'chiriladi
  • PartialType → yangilash uchun — barcha maydon ixtiyoriy

Keyingi dars

6-dars: Pipes — Built-in, Custom, Validation