Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. DTO nima
- 2.1 DTO va entity
- 2.2 Nega klass, interfeys emas
- 3. class-validator: takror va chuqurroq
- 3.1 Ikki qadam
- 3.2 ValidationError ni o'qish
- 3.3 Oddiy obyekt tuzog'i
- 3.4 Ko'p ishlatiladigan dekoratorlar
- 4. Dekoratorlar tartibi tuzog'i
- 4.1 Model: qoidalar qaysi tartibda yoziladi
- 4.2 Haqiqiy class-validator
- 5. O'z validatoringiz
- 5.1 @ValidatorConstraint + @Validate
- 5.2 registerDecorator — o'z dekoratoringiz
- 5.3 Nomsiz validator tuzog'i
- 6. class-transformer: tekshiruvdan oldin tayyorlash
- 6.1 @Transform va @Type
- 6.2 Nima uchun @Transform ichida tur tekshiruvi
- 7. Nest'da: ValidationPipe bilan birinchi uchrashuv
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): dekoratorni tanlang
- 2-mashq (o'rta): IsOptional ni modelga qo'shing
- 3-mashq (qiyin): xato daraxtini tekislash
- 11. Loyiha qadami: class-validator varianti (loyihaga kirmaydi)
- 11.1 Kanon qarori
- 11.2 Variantni o'zingiz sinash
- 11.3 vazifa.dto.ts
- 12. Real ishda
- Xulosa
- Manbalar
DTO va class-validator NestJS'da: dekoratorli qoidalar, ValidationError va o'z validatorlar
Qisqacha: DTO — mijozdan keladigan ma'lumotning shakli, oddiy klass. Uning maydonlari ustiga class-validator dekoratorlari qo'yiladi:
@IsString(),@Length(1, 100),@IsEmail(),@IsEnum(Rol),@IsOptional(). Tekshiruv ikki qadamda: class-transformer JSON obyektni klass nusxasiga aylantiradi (plainToInstance), keyinvalidate()qoidalarni bajaradi vaValidationErrorro'yxatini qaytaradi. Nest'da bu ishniValidationPipeqiladi. Eng ko'p uchraydigan tuzoq: bir maydondagi dekoratorlar pastdan yuqoriga ishlaydi.
Bu darsda
- DTO va entity (ombordagi to'liq yozuv) farqini tushuntira olasiz va nega DTO klass bo'lishi kerakligini ko'rasiz.
- class-validator'ning asosiy dekoratorlari bilan DTO yozasiz va
ValidationErrordaraxtini o'qiysiz. - O'z validatoringizni ikki usulda yozasiz:
@ValidatorConstraint+@ValidatevaregisterDecorator. @Transformva@Typebilan qiymatni tekshiruvdan oldin tayyorlaysiz.vazifalar-apining class-validator variantini ko'rasiz va nega u hozircha loyihaga kirmasligini bilasiz.
Oldin bilishingiz kerak: Backend validatorlari: Joi, class-validator va express-validator, Legacy dekoratorlar va reflect-metadata, Request lifecycle, Reflector, metadata va DiscoveryService.
O'tgan darsda Nest dekoratorlar qoldirgan metama'lumotni qanday o'qishini ko'rdik. Bu dars va keyingi beshta darsda shu bilim eng ko'p ishlatiladigan joyga — kirish tekshiruviga qo'llanadi.
1. Nega bu kerak?
Controller'lar darsida bitta ogohlantirish bor edi: @Body() body: YangiVazifa — tekshiruv emas. TypeScript turi ish vaqtida yo'q. Hujumchi {"matn": 5, "admin": true} yuborsa, u shu "tur" bilan metodga kiradi.
vazifalar-api da hozircha bu muammoni 25-qismdagi usul hal qiladi: handler boshida tekshir(YangiVazifaSxemasi, req.body, "body"). Ishlaydi, lekin har handler'da qo'lda yoziladi va @Req() ga bog'lanadi.
Nest'da kirishni tekshiradigan maxsus qatlam bor — pipe. Request lifecycle darsidagi tartibni eslang: middleware → guard → interceptor → pipe → handler. Pipe har parametrni handler'ga berishdan oldin ko'radi. Savol: pipe nimaga qarab tekshiradi?
Javob — parametr turiga. Lekin tur faqat klass bo'lsa ish vaqtida qoladi. Shuning uchun Nest dunyosida kirish shakli — klass, qoidalari — shu klass maydonlaridagi dekoratorlar. Bunday klass DTO deyiladi.
O'xshatish: bankdagi anketa blanki. Har katakcha yonida qoida yozilgan: "faqat raqam", "16 ta xona", "ixtiyoriy". Kassir (pipe) blankni qabul qilishdan oldin har katakni shu qoida bo'yicha tekshiradi. Blankni to'ldirgan odam qoidani o'zgartira olmaydi.
Nest loyihasiga ishga kirsangiz, birinchi ko'radigan narsalaringizdan biri — create-user.dto.ts kabi fayllar va ichidagi @IsEmail(). Shuning uchun bu dars standartni to'liq o'rgatadi, garchi bizning loyiha boshqa yo'lni tanlagan bo'lsa ham (sababi — o'lchov bilan, Zod va Standard Schema NestJS'da darsida).
2. DTO nima
2.1 DTO va entity
DTO (Data Transfer Object — ma'lumot uzatish obyekti) — bir qatlamdan boshqasiga o'tadigan ma'lumot shakli. Bu atamani Next.js'dagi DAL darsida ham ko'rgansiz. Backend'da u ko'pincha "mijoz nima yuborishi mumkin" degan ma'noda ishlatiladi.
Entity — ombordagi to'liq yozuv. vazifalar-api da bu Vazifa:
| Maydon | Vazifa (entity) |
YangiVazifaDto (POST) |
OzgarishDto (PATCH) |
|---|---|---|---|
id, versiya |
bor | yo'q | yo'q |
matn |
bor | majburiy | ixtiyoriy |
bajarildi |
bor | ixtiyoriy | ixtiyoriy |
teglar, yaratildi |
bor | yo'q (server hisoblaydi) | yo'q |
Nega entity'ni to'g'ridan-to'g'ri @Body() turi qilmaymiz? Chunki unda id, versiya bor — mijoz ularni yubormasligi kerak. Agar yuborgan qiymat omborga yetib borsa, bu ommaviy tayinlash (mass assignment) zaifligi (Validatsiya: nima uchun, qayerda va qanday). Har amal uchun alohida DTO — "shu amalda mijozdan faqat shu maydonlar".
2.2 Nega klass, interfeys emas
Bu farqni brauzerda ko'rib chiqamiz. Blok TypeScript'ni dekoratorlar bilan kompilyatsiya qiladi va reflect-metadata ishlatadi — Nest ham aynan shunday ishlaydi:
// @ts-options: {"experimentalDecorators": true, "emitDecoratorMetadata": true}
import "reflect-metadata";
interface YangiVazifa { matn: string }
const Belgi = (): PropertyDecorator => () => {};
class YangiVazifaDto {
@Belgi() matn!: string;
@Belgi() bajarildi?: boolean;
}
const tur = (k: object, m: string): unknown =>
Reflect.getMetadata("design:type", k, m);
console.log(tur(YangiVazifaDto.prototype, "matn"));
console.log(tur(YangiVazifaDto.prototype, "bajarildi"));
console.log(typeof YangiVazifaDto);
const bosh: YangiVazifa = { matn: "Non olish" };
console.log(Object.keys(bosh));Konsolda:
[Function: String]
[Function: Boolean]
function
[ 'matn' ]Klass ish vaqtida bor (function), va dekorator qo'yilgan har maydon uchun TypeScript design:type metama'lumotini yozib qo'ydi: String, Boolean. YangiVazifa interfeysi esa kompilyatsiyadan keyin butunlay yo'qoladi — undan ish vaqtida so'raydigan hech narsa qolmaydi. Pipe ham, class-validator ham klassdan o'qiydi. Qoida shundan kelib chiqadi: DTO — klass.
Tekshirib ko'ring: Hamkasbingiz
export interface CreateUserDto { email: string }yozdi va@Body() dto: CreateUserDtoqildi. GlobalValidationPipeyoqilgan. Noto'g'ri email bilan so'rov o'tadimi?
Javob
O'tadi. Interfeys ish vaqtida yo'q — Nest parametr turi sifatida Object ni ko'radi, unda hech qanday qoida yo'q. ValidationPipe tekshiradigan narsa topmaydi va qiymatni o'zgarishsiz uzatadi. DTO klass bo'lishi va maydonlarida class-validator dekoratorlari turishi shart.
3. class-validator: takror va chuqurroq
3.1 Ikki qadam
Backend validatorlari darsida class-validator'ni Zod bilan taqqoslagan edik. Asosiy g'oyani eslaymiz: tekshiruv ikki qadamda bo'ladi.
flowchart LR
A["JSON tana<br/>{ matn: '' }"] --> B["plainToInstance<br/>(class-transformer)"]
B --> C["YangiVazifaDto<br/>nusxasi"]
C --> D["validate()<br/>(class-validator)"]
D --> E{"xato bormi?"}
E -- "yo'q" --> F["[] — handler'ga"]
E -- "bor" --> G["ValidationError[]"]Birinchi qadam oddiy obyektni klass nusxasiga aylantiradi. Ikkinchi qadam o'sha nusxaning klassidan dekoratorlar qoldirgan qoidalarni topadi va bajaradi. Diagrammadagi asosiy narsa — o'rtadagi nusxa: qoidalar klassga bog'langan, oddiy obyektda ular yo'q.
Tajriba uchun alohida kichik loyiha qildik: Nest CLI bilan yaratilgan demo, ustiga class-validator 0.15.1 va class-transformer 0.5.1 (biz ishga tushirdik: Node 24.21.0, NestJS 12.2.0, TypeScript 6.0.3). Birinchi DTO:
import { plainToInstance } from 'class-transformer';
import {
IsInt, IsOptional, IsString, Length, Max, Min, validate,
} from 'class-validator';
import type { ValidationError } from 'class-validator';
class YangiVazifaDto {
@IsString()
@Length(1, 100)
matn!: string;
@IsOptional()
@IsInt()
@Min(1)
@Max(5)
muhimlik?: number;
}
// o'qish uchun: har xatodan uch maydon, bir qatorda
const qisqa = (x: ValidationError[]) => x.map((e) => ({
maydon: e.property, qiymat: e.value, qoidalar: e.constraints,
}));
const yaroqli = plainToInstance(YangiVazifaDto, {
matn: 'Non olish',
});
console.log(
yaroqli instanceof YangiVazifaDto,
JSON.stringify(await validate(yaroqli)),
);
const yomon = plainToInstance(YangiVazifaDto, {
matn: '', muhimlik: 9,
});
for (const e of qisqa(await validate(yomon))) {
console.log(JSON.stringify(e));
}Chiqish:
true []
{"maydon":"matn","qiymat":"","qoidalar":{"isLength":"matn must be longer than or equal to 1 characters"}}
{"maydon":"muhimlik","qiymat":9,"qoidalar":{"max":"muhimlik must not be greater than 5"}}Yaroqli nusxada xato yo'q — bo'sh massiv. Yaroqsizda ikkita xato: matn bo'sh, muhimlik 5 dan katta.
3.2 ValidationError ni o'qish
Har xato — ValidationError obyekti. Uning to'rt maydoni sizga kerak bo'ladi:
| Maydon | Nima | Misol |
|---|---|---|
property |
qaysi maydon | "matn" |
value |
kelgan qiymat | "" |
constraints |
buzilgan qoidalar: { qoidaNomi: xabar } |
{ isLength: "..." } |
children |
ichma-ich obyekt xatolari | keyingi darslarda |
constraints — obyekt: bitta maydon bir nechta qoidani buzishi mumkin, har biri o'z kaliti bilan. isLength, max — qoida nomlari, ular xabarni almashtirsangiz ham o'zgarmaydi. Mijozga javob yasashda shu kalitlarga tayanish mumkin.
children ichma-ich DTO'larda to'ladi va daraxt hosil qiladi. Uni tekis ro'yxatga aylantirish — «Xato daraxtini tekislash» mashqi.
3.3 Oddiy obyekt tuzog'i
Birinchi qadamni tashlab ketsak nima bo'ladi?
console.log(JSON.stringify(qisqa(await validate({ matn: 5 }))));[{"qoidalar":{"unknownValue":"an unknown value was passed to the validate function"}}]Tarjimasi: "validate funksiyasiga noma'lum qiymat berildi". { matn: 5 } — hech qaysi klassning nusxasi emas. class-validator unda qoidalarni topa olmadi va bitta umumiy xato berdi. E'tibor bering: xato matn haqida emas — maydon nomi ham, qoida ham yo'q.
Bu xulq forbidUnknownValues sozlamasidan keladi; biz o'rnatgan 0.15.1 versiyada u sukut bo'yicha yoqilgan. Backend validatorlari darsida bu tuzoqni "tekshiruv jim o'tadi" deb aytgan edik. Biz 0.15.1 da qayta tekshirdik: jim o'tmaydi, lekin baribir maydon qoidalari ishlamaydi. Xulosa o'zgarmaydi — plainToInstance siz validate foydasiz. Nest'da bu ikki qadamni ValidationPipe o'zi bajaradi, shuning uchun bu xatoga ko'pincha Nest'dan tashqarida (skript, worker, test) duch kelasiz.
3.4 Ko'p ishlatiladigan dekoratorlar
| Dekorator | Tekshiradi |
|---|---|
@IsString(), @IsBoolean(), @IsInt(), @IsNumber() |
JavaScript turi |
@Length(min, max), @MinLength, @MaxLength |
satr uzunligi |
@Min(n), @Max(n), @IsPositive() |
son oralig'i |
@IsEmail(), @IsUrl(), @IsUUID() |
format |
@IsEnum(Rol), @IsIn([...]) |
ruxsat etilgan qiymatlar |
@Matches(/regex/) |
naqsh |
@IsArray(), @ArrayMaxSize(n) |
massiv |
@IsOptional() |
qiymat yo'q bo'lsa — qolgan qoidalarni o'tkazib yuborish |
Har dekoratorga oxirgi argument sifatida sozlama beriladi: { message: "..." } — o'z xabaringiz, { each: true } — massivning har elementiga, { groups: [...] } — guruhlar (keyingi darslarda).
Foydalanuvchi ro'yxatdan o'tadigan DTO bilan sinadik:
enum Rol { Foydalanuvchi = 'foydalanuvchi', Admin = 'admin' }
class RoyxatdanOtishDto {
@IsEmail({}, { message: "email noto'g'ri" })
email!: string;
@IsEnum(Rol)
rol!: Rol;
@IsOptional()
@Length(2, 30)
ism?: string;
}{ email: "aziz@", rol: "boss" } uchun:
{"maydon":"email","qiymat":"aziz@","qoidalar":{"isEmail":"email noto'g'ri"}}
{"maydon":"rol","qiymat":"boss","qoidalar":{"isEnum":"rol must be one of the following values: foydalanuvchi, admin"}}email — o'zimiz bergan o'zbekcha xabar. rol — sukut inglizcha xabar: "rol quyidagi qiymatlardan biri bo'lishi kerak: foydalanuvchi, admin". ism umuman yo'q — @IsOptional() tufayli @Length tekshirilmadi.
@IsEmail ning birinchi argumenti — {} — email formati sozlamalari (masalan, domen talab qilinadimi). Ikkinchisi — xabar. Bu ikki argumentli shakl faqat format dekoratorlarida bor; adashtirsangiz, xabar sozlama o'rniga ketib qoladi va inglizcha xabar chiqaveradi.
Diqqat:
@IsOptional()qiymatundefinedbo'lganda ham,nullbo'lganda ham qolgan qoidalarni o'tkazib yuboradi.{"ism": null}— o'tadi. PATCH so'rovida bu jiddiy xato beradi — Mapped types va nested validatsiya darsida haqiqiy chiqish bilan ko'ramiz.
4. Dekoratorlar tartibi tuzog'i
4.1 Model: qoidalar qaysi tartibda yoziladi
Legacy dekoratorlar darsidagi qoidani eslang: bitta maydon ustidagi dekoratorlar pastdan yuqoriga qo'llanadi. class-validator har dekoratorni qo'llanganda qoidani ro'yxatga qo'shadi. Natijada ro'yxat tartibi — kodda ko'rganingizning teskarisi.
Buni class-validator'siz, kichik modelda ko'raylik. Model ham Nest kabi reflect-metadata da saqlaydi:
// @ts-options: {"experimentalDecorators": true}
import "reflect-metadata";
type Qoida = { nomi: string; tekshir: (v: unknown) => boolean };
const QOIDALAR = Symbol("qoidalar");
function qoidaQosh(nomi: string, tekshir: Qoida["tekshir"]) {
return (obyekt: object, maydon: string): void => {
console.log(`qo'shildi: ${maydon} ← ${nomi}`);
const eski: Record<string, Qoida[]> =
Reflect.getMetadata(QOIDALAR, obyekt) ?? {};
eski[maydon] = [...(eski[maydon] ?? []), { nomi, tekshir }];
Reflect.defineMetadata(QOIDALAR, eski, obyekt);
};
}
const SatrBolsin = () =>
qoidaQosh("satr", (v) => typeof v === "string");
const Uzunlik = (min: number, max: number) =>
qoidaQosh(`uzunlik ${min}–${max}`, (v) =>
typeof v === "string" && v.length >= min && v.length <= max);
class YangiVazifaDto {
@SatrBolsin()
@Uzunlik(1, 100)
matn!: string;
}
function tekshir(
klass: { prototype: object },
qiymat: Record<string, unknown>,
) {
const qoidalar: Record<string, Qoida[]> =
Reflect.getMetadata(QOIDALAR, klass.prototype) ?? {};
const xatolar: string[] = [];
for (const [maydon, royxat] of Object.entries(qoidalar)) {
for (const q of royxat) {
if (!q.tekshir(qiymat[maydon])) {
xatolar.push(`${maydon}: ${q.nomi}`);
}
}
}
return xatolar;
}
console.log(tekshir(YangiVazifaDto, { matn: "Non olish" }));
console.log(tekshir(YangiVazifaDto, { matn: 5 }));Konsolda:
qo'shildi: matn ← uzunlik 1–100
qo'shildi: matn ← satr
[]
[ 'matn: uzunlik 1–100', 'matn: satr' ]Kodda @SatrBolsin() tepada, lekin ro'yxatga birinchi bo'lib uzunlik tushdi. Shuning uchun { matn: 5 } uchun birinchi xato — "uzunlik", garchi asl muammo "satr emas" bo'lsa ham.
Model soddalashtirilgan: class-validator qoidalarni o'z ichki omborida saqlaydi (biz reflect-metadata ni ko'rsatish uchun ishlatdik) va har qoida uchun alohida xabar yasaydi. Tartib qoidasi esa aynan shunday.
4.2 Haqiqiy class-validator
Bitta xato yetarli bo'lsa, stopAtFirstError: true beriladi — har maydon uchun birinchi buzilgan qoida qaytadi. Ikki klass bilan sinadik — farqi faqat dekoratorlar tartibida:
class TartibA { @IsString() @Length(1, 100) matn!: string; }
class TartibB { @Length(1, 100) @IsString() matn!: string; }
for (const K of [TartibA, TartibB]) {
const x = await validate(plainToInstance(K, {}), {
stopAtFirstError: true,
});
console.log(K.name, JSON.stringify(x[0]?.constraints));
}TartibA {"isLength":"matn must be longer than or equal to 1 characters"}
TartibB {"isString":"matn must be a string"}{} — matn umuman yo'q. To'g'ri xabar — "satr bo'lishi kerak". TartibA da (@IsString tepada) xabar "uzunlik kamida 1 belgi" chiqdi — mijoz "bo'sh satr yubordim shekilli" deb adashadi. TartibB da (@IsString pastda) xabar to'g'ri.
Qoida: tur tekshiruvi (@IsString, @IsInt) — maydondagi dekoratorlarning eng pastida. Kanon tajribasida ham aynan shu xato bo'lgan: class-validator variantini yozayotganda {} uchun "1–100 belgi" chiqqan va testlar ushlagan. Variantning har DTO'sida shuning uchun izoh bor ("Loyiha qadami" bo'limiga qarang).
Tekshirib ko'ring:
@IsInt() @Min(1) limit!: numbermaydoniga"abc"keldi,stopAtFirstError: true. Qaysi xabar chiqadi va nega?
Javob
min xabari: "limit must not be less than 1". Dekoratorlar pastdan yuqoriga qo'llanadi — @Min birinchi tekshiriladi va "abc" uni buzadi. To'g'risi — @Min(1) @IsInt() limit (tur tekshiruvi pastda), shunda "must be an integer number" chiqadi.
5. O'z validatoringiz
Tayyor dekoratorlar yetmaydigan joy tez keladi: "teg # bilan boshlansin", "sana haqiqiy kalendar kuni bo'lsin". class-validator'da buning ikki yo'li bor.
5.1 @ValidatorConstraint + @Validate
Qoida — ValidatorConstraintInterface ni bajaradigan klass: validate() — true/false, defaultMessage() — xabar.
@ValidatorConstraint({ name: 'tegFormati' })
class TegFormati implements ValidatorConstraintInterface {
validate(qiymat: unknown): boolean {
return typeof qiymat === 'string'
&& /^#[\p{L}\p{N}_]{1,30}$/u.test(qiymat);
}
defaultMessage(a: ValidationArguments): string {
return `${a.property}: "#" bilan boshlangan so'z bo'lsin`;
}
}
class TegDto {
@Validate(TegFormati)
birinchi!: string;
}ValidationArguments — xabar yasash uchun kontekst: property (maydon nomi), value, object (butun DTO), constraints.
5.2 registerDecorator — o'z dekoratoringiz
@Validate(TegFormati) o'qishga noqulay. Uni @IsTeg() ga aylantiramiz:
function IsTeg(sozlama?: ValidationOptions): PropertyDecorator {
return (obyekt, maydon) => {
registerDecorator({
name: 'isTeg',
target: obyekt.constructor,
propertyName: String(maydon),
options: sozlama,
validator: TegFormati,
});
};
}
class TegDto {
@Validate(TegFormati)
birinchi!: string;
@IsTeg({ each: true })
boshqalar!: string[];
}{ birinchi: "uy", boshqalar: ["#ish", "bozor"] } uchun:
{"maydon":"birinchi","qiymat":"uy","qoidalar":{"tegFormati":"birinchi: \"#\" bilan boshlangan so'z bo'lsin"}}
{"maydon":"boshqalar","qiymat":["#ish","bozor"],"qoidalar":{"tegFormati":"boshqalar: \"#\" bilan boshlangan so'z bo'lsin"}}Ikki narsaga qarang. { each: true } — qoida massivning har elementiga: "#ish" o'tdi, "bozor" o'tmadi va butun maydon xato bo'ldi. constraints kaliti — tegFormati, ya'ni validator klassi nomi (@ValidatorConstraint({ name })), dekorator nomi emas.
validate() Promise<boolean> ham qaytarishi mumkin — masalan, "bu email bazada bormi". Bunda @ValidatorConstraint({ async: true }) qo'yiladi. Lekin bazaga murojaat qiladigan tekshiruvni DTO'ga qo'yishdan ehtiyot bo'ling. Har so'rov uchun qo'shimcha so'rov bazaga boradi. Baribir ombor qatlami ham ushlashi kerak: ikki so'rov bir vaqtda "yo'q" deb ko'rishi mumkin (poyga holati).
5.3 Nomsiz validator tuzog'i
Kanon tajribasida yana bir haqiqiy xato topilgan. limit va offset uchun "butun son, oraliqda" qoidasini bitta yordamchi funksiya yasagan, ichida nomsiz klass bilan:
// XATO: har chaqiruvda yangi klass, lekin nomi bir xil — "Oraliq"
const butunOraliq = (nom: string, min: number, max: number) => {
class Oraliq implements ValidatorConstraintInterface {
validate(v: unknown) {
return Number(v) >= min && Number(v) <= max;
}
}
return Validate(Oraliq);
};Natija: limit=0 o'tib ketdi (200). Sabab: class-validator constraint klasslarini nomi bo'yicha saqlaydi. Ikkala chaqiruv Oraliq nomli klass yasadi; ikkinchisi birinchisining o'rnini egalladi va limit uchun offset ning oralig'i (0..10 000) ishladi. Tuzatish — har klassga noyob nom:
ValidatorConstraint({ name: `${nom}Oraliq` })(Oraliq);Bu xato testlarsiz sezilmasdi: hech qanday ogohlantirish yo'q, faqat chegaradagi qiymat noto'g'ri o'tadi. 25-qismdagi limit=0 testlari shuni ushladi.
6. class-transformer: tekshiruvdan oldin tayyorlash
6.1 @Transform va @Type
Zod'da .trim() sxemaning bir qismi edi. class-validator faqat tekshiradi, o'zgartirmaydi. Qiymatni o'zgartirish — class-transformer'ning ishi, va u birinchi qadamda (plainToInstance) bajariladi:
class KesilganDto {
@Transform(({ value }) =>
typeof value === 'string' ? value.trim() : value)
@IsString()
@Length(1, 100)
matn!: string;
@Type(() => Number)
@IsInt()
limit!: number;
}@Transform(fn)— maydon qiymatinifnqaytargani bilan almashtiradi.@Type(() => Number)— qiymatni berilgan turga aylantiradi (Number("20")).
Ikki kirish bilan sinadik — {"matn":" Non ","limit":"20"} va {"matn":" ","limit":"abc"}. Har biri uchun: nusxa, limit turi (birinchisida) va xatolar:
{"matn":"Non","limit":20} number []
{"matn":"","limit":null} [{"maydon":"matn","qiymat":"","qoidalar":{"isLength":"matn must be longer than or equal to 1 characters"}},{"maydon":"limit","qiymat":null,"qoidalar":{"isInt":"limit must be an integer number"}}]Birinchisi: bo'sh joylar kesildi, "20" son bo'ldi, xato yo'q. Ikkinchisi qiziqroq: " " kesilib "" bo'ldi va keyin @Length uni ushladi — kesish tekshiruvdan oldin bo'lgani uchun to'g'ri. "abc" esa Number("abc") = NaN bo'ldi (JSON'da NaN null bo'lib ko'rinadi). @IsInt uni rad etdi — yaxshi. Lekin @IsInt bo'lmaganida, NaN handler'gacha yetib borardi.
6.2 Nima uchun @Transform ichida tur tekshiruvi
typeof value === 'string' ? value.trim() : value — nega shunchaki value.trim() emas? Mijoz {"matn": 5} yuborsa, (5).trim() — TypeError va server 500 qaytaradi. Biz esa "satr bo'lishi kerak" (422) xabarini kutamiz. Transform qiymatni tanimasa — o'zgarishsiz qaytarsin, tur xatosini validator bersin.
Tekshirib ko'ring:
@Transform(({ value }) => value.toLowerCase())yozilgan{"email": ["a@b.uz"]}keldi. Nima bo'ladi?
Javob
TypeError: value.toLowerCase is not a function — massivda bunday metod yo'q. Xato plainToInstance ichida, tekshiruvdan oldin chiqadi, shuning uchun mijoz 400 emas, 500 oladi. To'g'risi — avval turini tekshirish: typeof value === "string" ? value.toLowerCase() : value.
7. Nest'da: ValidationPipe bilan birinchi uchrashuv
Endi bu hammasini Nest o'zi bajaradi. Ilovaga global pipe qo'shiladi, controller esa DTO turini yozadi:
// main.ts
app.useGlobalPipes(new ValidationPipe());
// controller
@Post('yangi')
yangi(@Body() b: YangiDto) {
return b;
}ValidationPipe parametr turini (design:paramtypes dan YangiDto) oladi, plainToInstance va validate ni bajaradi. Xato bo'lsa — 400. Kanon tajribasi bitta controller'ni pipe'siz va sukut ValidationPipe() bilan yurgizgan (natijalar Vitest ichida olingan, NestJS 12.2.0):
POST /yangi {"matn":5}
pipe YO'Q → 201 {"qiymat":{"matn":5},"klass":"Object",…}
new ValidationPipe() — sukut → 400 {"message":["matn must be longer than or equal to 1 and shorter than or equal to 100 characters","matn must be a string"],"error":"Bad Request","statusCode":400}Pipe'siz {"matn": 5} handler'ga yetdi. Pipe bilan — 400 va message massivida ikkala buzilgan qoida. Xabarlar inglizcha va Nest'ning o'z formatida (statusCode, error) — bizning Problem Details emas. Bu formatni o'zgartirish yo'li — keyingi darsda.
Endi yomon xabar. Xuddi shu tajribada:
POST /yangi {"matn":"Non","admin":true}
new ValidationPipe() — sukut → 201 {"qiymat":{"matn":"Non","admin":true},…}Sukut sozlamada admin: true o'tib ketdi. DTO'da yo'q maydon tekshirilmaydi ham, olib tashlanmaydi ham. Bu — ValidationPipe ning eng xavfli sukuti, keyingi darsning birinchi mavzusi.
8. Hujumchi nigohi
DTO — tashqi dunyo ma'lumotining birinchi to'sig'i. Hujumchi nimani sinaydi:
- Ortiqcha maydon.
{"matn":"x","admin":true,"egasi_id":1}. SukutValidationPipeularni o'tkazadi; controllerbodyni to'g'ridan omborga bersa — ommaviy tayinlash. Himoya —whitelist+forbidNonWhitelisted(keyingi dars) va har amalga alohida DTO. - Tur aralashtirish.
{"matn": {"$ne": 1}},{"matn": ["a"]}. Himoya — har maydonda tur dekoratori (@IsString) va@Transformichida tur tekshiruvi (aks holda 500). - Ulkan kirish. 100 000 elementli massiv yoki 10 MB satr. Himoya —
@ArrayMaxSize,@Length/@MaxLengthva tana hajmi chegarasi (express.json({ limit }), 25-qism). - Sekin regex.
@Matches(/^(a+)+$/)— ReDoS (Qo'lda routing va query parametrlar). Himoya — oddiy, chegaralangan naqshlar ({1,30}) va uzunlikni regexdan oldin tekshirish (dekorator tartibi — pastda). - Qoida chetlab o'tish. Nomsiz constraint klassi (yuqoridagi
limit=0) yoki@IsOptional+null. Himoya — chegaraviy qiymatlar uchun testlar.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Noto'g'ri ma'lumot jim o'tadi | DTO interfeys yoki type |
DTO — klass |
an unknown value was passed to the validate function |
validate() ga oddiy obyekt |
avval plainToInstance |
{} uchun "uzunlik" xabari |
tur dekoratori tepada | @IsString() eng pastga |
TypeError: value.trim is not a function (500) |
@Transform tur tekshirmaydi |
typeof value === "string" ? … : value |
| Bir qoida o'rniga boshqasi ishlaydi | constraint klasslari nomi bir xil | @ValidatorConstraint({ name }) noyob |
NaN handler'ga yetadi |
@Type(() => Number) bor, @IsInt yo'q |
aylantirishdan keyin tur tekshiruvi |
10. Mashqlar
Mashqlar kurs/mashqlar/30/15-dto/ da.
1-mashq (oson): dekoratorni tanlang
- Maydon email bo'lsin —
@() - Qiymat faqat
"csv"yoki"json"—@(["csv", "json"]) - Maydon bo'lmasa, qolgan qoidalar tekshirilmasin —
@() - Massivning har elementi satr —
@IsString({ : true }) - JSON obyektni DTO nusxasiga aylantiradigan funksiya —
Yechim
- IsEmail — format dekoratori; xabar ikkinchi argumentda:
@IsEmail({}, { message }). - IsIn — ro'yxatdagi qiymatlar; TypeScript
enumbo'lsa —@IsEnum. - IsOptional —
undefinedvanullikkalasida ham o'tkazib yuboradi. - each —
{ each: true }har dekoratorda ishlaydi. - plainToInstance — class-transformer'dan; usiz
validate()qoidalarni topmaydi.
2-mashq (o'rta): IsOptional ni modelga qo'shing
Darsdagi qoidalar modelini dekoratorsiz shaklda oling: qoidalar oddiy massivda, { maydon, nomi, tekshir } ko'rinishida. tekshir(qoidalar, qiymat) funksiyasini yozing. Qoidalar ro'yxatida { maydon, nomi: "ixtiyoriy" } bo'lsa va maydon qiymati undefined yoki null bo'lsa — shu maydonning qolgan hamma qoidasi o'tkazib yuborilsin (class-validator'dagi kabi). Ishora: avval ixtiyoriy maydonlarni Set ga yig'ing (Set), filter bilan.
Yechim
function tekshir(qoidalar, qiymat) {
const ixtiyoriy = new Set(
qoidalar
.filter((q) => q.nomi === "ixtiyoriy")
.map((q) => q.maydon),
);
const xatolar = [];
for (const q of qoidalar) {
if (q.nomi === "ixtiyoriy") continue;
const v = qiymat[q.maydon];
if (ixtiyoriy.has(q.maydon) && (v === undefined || v === null)) {
continue;
}
if (!q.tekshir(v)) xatolar.push(`${q.maydon}: ${q.nomi}`);
}
return xatolar;
}
const satr = (v) => typeof v === "string";
const qoidalar = [
{ maydon: "matn", nomi: "satr", tekshir: satr },
{ maydon: "ism", nomi: "ixtiyoriy" },
{ maydon: "ism", nomi: "satr", tekshir: satr },
];
console.log(tekshir(qoidalar, { matn: "Non" }));
console.log(tekshir(qoidalar, { matn: "Non", ism: null }));
console.log(tekshir(qoidalar, { matn: 5, ism: 7 }));Konsolda:
[]
[]
[ 'matn: satr', 'ism: satr' ]Ikkinchi qator — aynan @IsOptional ning null tuzog'i: ism: null hech qanday xatosiz o'tdi. Agar null ni rad etish kerak bo'lsa, shartdan v === null ni olib tashlash yetadi — class-validator'da buning o'rniga @ValidateIf((o) => o.ism !== undefined) yoziladi («Loyiha qadami» bo'limidagi OzgarishDto ga qarang).
3-mashq (qiyin): xato daraxtini tekislash
Ichma-ich DTO'da ValidationError daraxt bo'ladi: children ichida yana xatolar. Mijozga esa tekis ro'yxat kerak: { joy: "body.vazifalar.1.matn", xabar: "..." } (25-qismdagi joy formati). yoy(xatolar, yol) funksiyasini yozing: har xato uchun joy — yol + . + property; constraints dagi har xabar — alohida yozuv; children bo'lsa — o'sha joy bilan rekursiv (Rekursiya asoslari).
Yechim
function yoy(xatolar, yol, natija = []) {
for (const x of xatolar) {
const joy = `${yol}.${x.property}`;
for (const xabar of Object.values(x.constraints ?? {})) {
natija.push({ joy, xabar });
}
if (x.children?.length) yoy(x.children, joy, natija);
}
return natija;
}
const daraxt = [
{
property: "vazifalar",
children: [
{
property: "1",
children: [
{
property: "matn",
constraints: { isLength: "matn 1–100 belgi" },
},
],
},
],
},
{
property: "ortiqcha",
constraints: { whitelistValidation: "..." },
},
];
for (const x of yoy(daraxt, "body")) console.log(x.joy, "—", x.xabar);Konsolda:
body.vazifalar.1.matn — matn 1–100 belgi
body.ortiqcha — ...Kanon tajribasidagi DtoPipe aynan shu funksiyani ishlatadi, bitta qo'shimcha bilan: whitelistValidation qoidasining xabari noma'lum parametr: <maydon> ga almashtiriladi — Zod variantidagi xabarga mos bo'lishi uchun. Pipe'ning o'zini keyingi darsda ko'ramiz.
11. Loyiha qadami: class-validator varianti (loyihaga kirmaydi)
11.1 Kanon qarori
vazifalar-api da kirish tekshiruvi Zod'da qoladi — 25-qismdagi o'sha sxemalar (src/vazifalar/sxema.ts) o'zgarmaydi. Nega — Zod va Standard Schema NestJS'da darsida o'lchov bilan ko'ramiz: tezlik, bog'liqliklar, "bitta manba".
Lekin kanon class-validator'ni chetga surib qo'ymagan. Xuddi shu endpointlar uchun to'liq ishlaydigan variant yozilgan va 25–27-qismlarning HTTP testlari shu variant bilan ham yurgizilgan. Natija (kanon o'lchovi, IlmHamroh, 2026-10):
Test Files 13 passed (13)
Tests 52 passed (52)Ya'ni class-validator bu loyihaga yaraydi. Kanon tanlovi — o'lchab asoslangan muqobil, class-validator "noto'g'ri" degani emas.
11.2 Variantni o'zingiz sinash
Variant loyihaning nest tarmog'iga (Nest ko'chishi shu tarmoqda boradi) kirmaydi. Sinab ko'rmoqchi bo'lsangiz — nest dan ajratilgan alohida git tarmog'ida:
git switch -c tajriba/class-validator nest
npm install -E class-validator@0.15.1 class-transformer@0.5.1nest tarmog'iga qaytish — git switch nest. Keyingi darslarda shu tarmoqda pipe va ichma-ich DTO'lar qo'shiladi.
11.3 vazifa.dto.ts
Fayl joyi — src/vazifalar/dto/vazifa.dto.ts. Xabarlar Zod sxemasidagi bilan aynan bir xil yozilgan — shuning uchun o'sha testlar ikkala variantda o'tadi. Kanon faylida qatorlar uzun edi — telefon uchun bo'ldik va izohlarni qisqartirdik (kod ma'nosi aynan). Bo'lingan nusxani tsc --noEmit bilan tekshirdik.
// vazifa.dto.ts — 25-qism Zod sxemalarining class-validator +
// class-transformer shakli (tajriba varianti). Xabarlar Zod'dagi
// bilan bir xil — o'sha HTTP testlari shu variant bilan ham o'tadi
import { Transform } from "class-transformer";
import {
IsBoolean, IsIn, IsOptional, IsString, Length, ValidateIf,
} from "class-validator";
import type { TransformFnParams } from "class-transformer";
import { MAKS_MATN } from "../sxema.ts";
/** Zod `.trim()` o'rnida: faqat satr bo'lsa (aks holda
* IsString xatosi chiqsin) */
const kesilgan = ({ value }: TransformFnParams): unknown =>
typeof value === "string" ? value.trim() : value;
const MATN_XABARI =
`matn 1–${String(MAKS_MATN)} belgi bo'lishi kerak`;
/** POST /v1/vazifalar */
export class YangiVazifaDto {
@Transform(kesilgan)
// TUZOQ: dekoratorlar PASTDAN YUQORIGA bajariladi — tur
// tekshiruvi (IsString) eng pastda turadi
@Length(1, MAKS_MATN, { message: MATN_XABARI })
@IsString({ message: "matn — satr bo'lishi kerak" })
matn!: string;
@IsOptional()
@IsBoolean({ message: "bajarildi — true yoki false" })
bajarildi?: boolean;
}
/** PUT /:id — hamma yoziladigan maydon majburiy */
export class ToliqVazifaDto {
@Transform(kesilgan)
@Length(1, MAKS_MATN, { message: MATN_XABARI })
@IsString({ message: "matn — satr bo'lishi kerak" })
matn!: string;
@IsBoolean({ message: "bajarildi — true yoki false" })
bajarildi!: boolean;
}
/**
* PATCH (Merge Patch): ikkala maydon ixtiyoriy, lekin `null` —
* xato. TUZOQ: @IsOptional() null'ni ham "yo'q" deb o'tkazadi —
* shuning uchun ValidateIf(!== undefined). "Kamida bitta maydon" —
* obyekt darajasidagi qoida yo'q: controller tekshiradi
*/
export class OzgarishDto {
@ValidateIf((o: OzgarishDto) => o.matn !== undefined)
@Transform(kesilgan)
@Length(1, MAKS_MATN, { message: MATN_XABARI })
@IsString({ message: "matn — satr bo'lishi kerak" })
matn?: string;
@ValidateIf((o: OzgarishDto) => o.bajarildi !== undefined)
@IsBoolean({ message: "bajarildi — true yoki false" })
bajarildi?: boolean;
}
/** POST /v1/eksportlar — Zod discriminatedUnion o'rnida.
* "json + ajratgich" taqiqi — obyekt darajasidagi qoida,
* controller'da */
export class EksportDto {
@IsOptional()
@IsIn(["csv", "json"], {
message: "format — \"csv\" yoki \"json\"",
})
format?: "csv" | "json";
@IsOptional()
@IsIn([",", ";"], { message: "ajratgich — \",\" yoki \";\"" })
ajratgich?: "," | ";";
}Uchta joyni Zod sxemasi bilan solishtiring:
ValidateIf. Zod'daMatn.optional()—undefinedni o'tkazadi,nullni rad etadi. class-validator'da@IsOptional()ikkalasini o'tkazadi, shuning uchun PATCH'da@ValidateIf((o) => o.matn !== undefined)— "faqat maydon berilgan bo'lsa tekshir".- "Kamida bitta maydon". Zod'da
.refine(...)sxemaning o'zida. class-validator'da obyekt darajasidagi qoida yo'q — bu tekshiruv controller'ga ko'chdi. - Eksport. Zod'da
discriminatedUnion:csvbo'lsaajratgichmumkin,jsonbo'lsa — yo'q. DTO'da ikki mustaqil ixtiyoriy maydon qoldi, "json + ajratgich" taqiqi yana controller'da.
Shu uch joy — class-validator'ning tabiiy chegarasi: qoidalar maydonga bog'langan. Maydonlar orasidagi qoida uchun o'z validator klassi yoki controller kodi kerak.
Hali commit qilmang. nest tarmog'ida bu darsda o'zgarish yo'q; loyihaning keyingi commit'i Javobni serializatsiya qilish darsida bo'ladi.
12. Real ishda
- Nest loyihalarining aksariyati —
src/<modul>/dto/create-*.dto.ts,update-*.dto.tsva globalValidationPipe. Nest hujjatidagi "Validation" bo'limi aynan shu yo'l bilan yozilgan. - Review'dagi savollar: "bu DTO interfeys emasmi?", "
@IsStringpastdami?", "@Transformturini tekshiradimi?", "massivda@ArrayMaxSizebormi?". - Intervyu: "DTO va entity farqi nima?", "nega DTO klass bo'lishi kerak?", "
plainToInstancenima qiladi?", "o'z validatorni qanday yozasiz?". - Ehtiyot: class-validator'ga qo'shimcha paket — class-transformer — 2021-yildan beri yangi versiya chiqarmagan (npm ma'lumoti, 2026-10). Bu "ishlamaydi" degani emas, lekin uzoq muddatga tanlashda e'tiborga olinadi.
Xulosa
- DTO — mijoz yuboradigan ma'lumot shakli; har amalga alohida, entity'dan ajratilgan; klass, interfeys emas.
- class-validator ikki qadamda:
plainToInstance(nusxa +@Transform/@Type) →validate()→ValidationError[](property,value,constraints,children). - Dekoratorlar pastdan yuqoriga qo'llanadi: tur tekshiruvi eng pastda.
- O'z qoida —
@ValidatorConstraint({ name })+@ValidateyokiregisterDecorator; nom noyob bo'lsin. @IsOptionalnullni ham o'tkazadi;@Transformichida tur tekshiring.- Kanon class-validator variantini to'liq yozgan (52/52 test), lekin loyihada Zod qoladi.
Keyingi dars: ValidationPipe sozlamalari va tuzoqlari — whitelist, forbidNonWhitelisted, transform, enableImplicitConversion ("false" → true!), exceptionFactory va pipe'ni global yoki lokal ulash.
Manbalar
- NestJS hujjati — Validation (
ValidationPipe, DTO, class-validator): https://docs.nestjs.com/techniques/validation - class-validator (dekoratorlar,
ValidationError, custom validatorlar): https://github.com/typestack/class-validator - class-transformer (
plainToInstance,@Transform,@Type): https://github.com/typestack/class-transformer - OWASP — Mass Assignment Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- Kanon (IlmHamroh, 2026-10-27): class-validator varianti, 25–27 HTTP testlari 52/52,
ValidationPipetajribasi (NestJS 12.2.0, Vitest ichida). Demo DTO'lar va xatolar chiqishi — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, class-validator 0.15.1, class-transformer 0.5.1, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!