Usullar10 min read

Validatsiya

Veb ilovaga yuborilgan har qanday ma'lumotning to'g'riligini tekshirish eng yaxshi amaliyotdir. Kelayotgan so'rovlarni avtomatik validatsiya qilish uchun Nest bir nechta tayyor pip

Veb ilovaga yuborilgan har qanday ma'lumotning to'g'riligini tekshirish eng yaxshi amaliyotdir. Kelayotgan so'rovlarni avtomatik validatsiya qilish uchun Nest bir nechta tayyor pipe larni taqdim etadi:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe

ValidationPipe kuchli class-validator paketidan va uning deklarativ validatsiya dekoratorlaridan foydalanadi. ValidationPipe barcha kiruvchi klient payloadlari uchun validatsiya qoidalarini majburan qo'llashning qulay usulini beradi; maxsus qoidalar har bir moduldagi lokal class/DTO deklaratsiyalarida oddiy annotatsiyalar orqali belgilanadi.

Umumiy ko'rinish

Pipes bobida biz oddiy pipe larni qurish va ularni controllerlarga, metodlarga yoki global ilovaga bog'lash jarayonini ko'rib chiqdik. Ushbu bob mavzularini yaxshiroq tushunish uchun o'sha bobni qayta ko'rib chiqishni unutmang. Bu yerda esa ValidationPipe ning turli real hayot foydalanish holatlarini ko'rib chiqamiz va uning ayrim ilg'or sozlash imkoniyatlaridan qanday foydalanishni ko'rsatamiz.

Ichki ValidationPipe dan foydalanish

Uni ishlatishni boshlash uchun avval kerakli bog'liqlikni o'rnatamiz.

Terminal
1$ npm i --save class-validator class-transformer
Hint

ValidationPipe@nestjs/common paketidan eksport qilinadi.

Bu pipe class-validator va class-transformer kutubxonalaridan foydalangani uchun, ko'plab opsiyalar mavjud. Siz bu sozlamalarni pipe ga uzatiladigan konfiguratsiya obyektida belgilaysiz. Quyida ichki opsiyalar keltirilgan:

TypeScript
1export interface ValidationPipeOptions extends ValidatorOptions {
2  transform?: boolean;
3  disableErrorMessages?: boolean;
4  exceptionFactory?: (errors: ValidationError[]) => any;
5}

Bularga qo'shimcha ravishda, barcha class-validator opsiyalari (ya'ni ValidatorOptions interfeysidan meros bo'lganlar) ham mavjud:

OptionTypeDescription
enableDebugMessagesbooleanAgar true bo'lsa, validator nimadir noto'g'ri bo'lsa konsolga qo'shimcha ogohlantirish xabarlarini chiqaradi.
skipUndefinedPropertiesbooleanAgar true bo'lsa, validator tekshirilayotgan obyektidagi undefined bo'lgan barcha xossalarni validatsiyadan o'tkazmaydi.
skipNullPropertiesbooleanAgar true bo'lsa, validator tekshirilayotgan obyektidagi null bo'lgan barcha xossalarni validatsiyadan o'tkazmaydi.
skipMissingPropertiesbooleanAgar true bo'lsa, validator tekshirilayotgan obyektidagi null yoki undefined bo'lgan barcha xossalarni validatsiyadan o'tkazmaydi.
whitelistbooleanAgar true bo'lsa, validator validatsiyadan o'tgan (qaytarilgan) obyektni validatsiya dekoratorlari ishlatilmagan xossalardan tozalaydi.
forbidNonWhitelistedbooleanAgar true bo'lsa, whitelistdan tashqari xossalarni kesib tashlash o'rniga validator istisno chiqaradi.
forbidUnknownValuesbooleanAgar true bo'lsa, noma'lum obyektlarni validatsiya qilish urinishlari darhol muvaffaqiyatsiz bo'ladi.
disableErrorMessagesbooleanAgar true bo'lsa, validatsiya xatolari klientga qaytarilmaydi.
errorHttpStatusCodenumberBu sozlama xato bo'lganda qaysi istisno turi ishlatilishini belgilash imkonini beradi. Default holatda BadRequestException tashlanadi.
exceptionFactoryFunctionValidatsiya xatolari massividan istisno obyektini yaratib qaytaradi.
groupsstring[]Obyektni validatsiya qilishda ishlatiladigan guruhlar.
alwaysbooleanDekoratorlar uchun always opsiyasining defaultini belgilaydi. Default dekorator opsiyalarida override qilinishi mumkin.
strictGroupsbooleanAgar groups berilmasa yoki bo'sh bo'lsa, kamida bitta guruhi bo'lgan dekoratorlarni e'tiborsiz qoldiradi.
dismissDefaultMessagesbooleanAgar true bo'lsa, validatsiya default xabarlardan foydalanmaydi. Xabar aniq ko'rsatilmagan bo'lsa, xato xabari har doim undefined bo'ladi.
validationError.targetbooleanValidationError ichida target ko'rsatilishini belgilaydi.
validationError.valuebooleanValidationError ichida validatsiyalangan qiymat ko'rsatilishini belgilaydi.
stopAtFirstErrorbooleanTrue bo'lsa, berilgan xossa uchun validatsiya birinchi xatodan so'ng to'xtaydi. Default false.
Notice

class-validator paketi haqida batafsil ma'lumotni uning repozitoriyidan toping.

Avto-validatsiya

ValidationPipe ni ilova darajasida bog'lab boshlaymiz, shunda barcha endpointlar noto'g'ri ma'lumot olishdan himoyalanadi.

TypeScript
1async function bootstrap() {
2  const app = await NestFactory.create(AppModule);
3  app.useGlobalPipes(new ValidationPipe());
4  await app.listen(process.env.PORT ?? 3000);
5}
6bootstrap();

Pipe ni sinab ko'rish uchun oddiy endpoint yaratamiz.

TypeScript
1@Post()
2create(@Body() createUserDto: CreateUserDto) {
3  return 'This action adds a new user';
4}
Hint

TypeScript generics yoki interfeyslar haqida metadata saqlamaganligi sababli, ularni DTOlarda ishlatsangiz, ValidationPipe kiruvchi ma'lumotlarni to'g'ri validatsiya qila olmasligi mumkin. Shu sababli, DTOlarda aniq klasslardan foydalanishni ko'rib chiqing.

Hint

DTOlarni import qilayotganda type-only importdan foydalana olmaysiz, chunki u runtime da olib tashlanadi, ya'ni import {{ '{' }} CreateUserDto {{ '}' }} ishlating, import type {{ '{' }} CreateUserDto {{ '}' }} emas.

Endi CreateUserDto ga bir nechta validatsiya qoidalarini qo'shishimiz mumkin. Buni class-validator paketi taqdim etgan dekoratorlar yordamida qilamiz, ular batafsil bu yerda tasvirlangan. Shu tarzda CreateUserDto ishlatadigan har qanday route avtomatik ravishda bu qoidalarni majburan qo'llaydi.

TypeScript
1import { IsEmail, IsNotEmpty } from 'class-validator';
2
3export class CreateUserDto {
4  @IsEmail()
5  email: string;
6
7  @IsNotEmpty()
8  password: string;
9}

Bu qoidalar bilan, agar so'rovimizda email xossasi noto'g'ri bo'lsa, ilova avtomatik ravishda 400 Bad Request kodi bilan javob beradi va quyidagi response body ni qaytaradi:

JSON
1{
2  "statusCode": 400,
3  "error": "Bad Request",
4  "message": ["email must be an email"]
5}

So'rov body larini validatsiya qilishdan tashqari, ValidationPipe ni so'rov obyektining boshqa xossalari bilan ham ishlatish mumkin. Masalan, endpoint pathida :id qabul qilishni xohlaymiz. Bu so'rov parametri uchun faqat sonlar qabul qilinishi kerak bo'lsa, quyidagi konstruktsiyadan foydalanamiz:

TypeScript
1@Get(':id')
2findOne(@Param() params: FindOneParams) {
3  return 'This action returns a user';
4}

FindOneParams, DTOga o'xshab, class-validator yordamida validatsiya qoidalarini belgilaydigan klass. U quyidagicha ko'rinadi:

TypeScript
1import { IsNumberString } from 'class-validator';
2
3export class FindOneParams {
4  @IsNumberString()
5  id: string;
6}

Batafsil xatolarni o'chirish

Xato xabarlari so'rovda nima noto'g'ri bo'lganini tushuntirish uchun foydali bo'lishi mumkin. Biroq, ayrim production muhitlarida batafsil xatolarni o'chirish afzal. Buni ValidationPipe ga opsiyalar obyektini uzatish orqali qiling:

TypeScript
1app.useGlobalPipes(
2  new ValidationPipe({
3    disableErrorMessages: true,
4  }),
5);

Natijada, batafsil xato xabarlari response body da ko'rsatilmaydi.

Xossalarni tozalash

ValidationPipe metod handleriga yetib kelmasligi kerak bo'lgan xossalarni ham filtrlashi mumkin. Bunda biz qabul qilinadigan xossalarni whitelist qilishimiz mumkin, va whitelistga kirmagan har qanday xossa natijaviy obyektlardan avtomatik olib tashlanadi. Masalan, handler email va password xossalarini kutadi, ammo so'rovda age ham bo'lsa, bu xossa DTOdan avtomatik olib tashlanadi. Buni yoqish uchun whitelist ni true qiling.

TypeScript
1app.useGlobalPipes(
2  new ValidationPipe({
3    whitelist: true,
4  }),
5);

true bo'lganda, whitelistga kirmagan xossalar (validatsiya klassida hech qanday dekoratorga ega bo'lmaganlar) avtomatik olib tashlanadi.

Muqobil ravishda, whitelistga kirmagan xossalar mavjud bo'lsa, so'rovni to'xtatib foydalanuvchiga xato javobini qaytarishingiz mumkin. Buni yoqish uchun whitelist ni true qilib qo'ying va qo'shimcha ravishda forbidNonWhitelisted xossasini true qiling.

Payload obyektlarini transformatsiya qilish

Tarmoq orqali keladigan payloadlar oddiy JavaScript obyektlaridir. ValidationPipe payloadlarni ularning DTO klasslariga mos tiplashtirilgan obyektlarga avtomatik transformatsiya qilishi mumkin. Avto-transformatsiyani yoqish uchun transform ni true qiling. Buni metod darajasida ham qilish mumkin:

TypeScript
cats.controller
1@Post()
2@UsePipes(new ValidationPipe({ transform: true }))
3async create(@Body() createCatDto: CreateCatDto) {
4  this.catsService.create(createCatDto);
5}

Bu xatti-harakatni global yoqish uchun global pipe da opsiyani belgilang:

TypeScript
1app.useGlobalPipes(
2  new ValidationPipe({
3    transform: true,
4  }),
5);

Avto-transformatsiya yoqilganda, ValidationPipe primitive turlarni ham konvertatsiya qiladi. Quyidagi misolda findOne() metodi extracted id path parametrini qabul qiladi:

TypeScript
1@Get(':id')
2findOne(@Param('id') id: number) {
3  console.log(typeof id === 'number'); // true
4  return 'This action returns a user';
5}

Default holatda, har bir path parametri va query parametri tarmoq orqali string sifatida keladi. Yuqoridagi misolda biz id tipini number deb ko'rsatdik (metod signatureda). Shuning uchun ValidationPipe string identifikatorni avtomatik ravishda songa aylantirishga urinadi.

Aniq konvertatsiya

Yuqoridagi bo'limda ValidationPipe kutilgan tipga asoslanib query va path parametrlarini yashirincha transformatsiya qilishini ko'rsatdik. Biroq, bu funksiya avto-transformatsiya yoqilgan bo'lgandagina ishlaydi.

Muqobil ravishda (avto-transformatsiya o'chirilgan bo'lsa), ParseIntPipe yoki ParseBoolPipe yordamida qiymatlarni aniq cast qilishingiz mumkin (eslatma: ParseStringPipe kerak emas, chunki oldin aytilganidek, har bir path parametri va query parametri default holatda string bo'lib keladi).

TypeScript
1@Get(':id')
2findOne(
3  @Param('id', ParseIntPipe) id: number,
4  @Query('sort', ParseBoolPipe) sort: boolean,
5) {
6  console.log(typeof id === 'number'); // true
7  console.log(typeof sort === 'boolean'); // true
8  return 'This action returns a user';
9}
Hint

ParseIntPipe va ParseBoolPipe@nestjs/common paketidan eksport qilinadi.

Mapped types

CRUD (Create/Read/Update/Delete) kabi funksiyalarni qurayotganda, ko'pincha bazaviy entity tipining variantlarini yaratish foydali bo'ladi. Nest bu vazifani yengillashtirish uchun type transformatsiyalarini bajaradigan bir nechta yordamchi funksiyalarni taqdim etadi.

Warning Agar ilovangiz @nestjs/swagger paketidan foydalansa, Mapped Types haqida batafsil ma'lumot uchun bu bobga qarang. Xuddi shuningdek, @nestjs/graphql paketidan foydalansangiz bu bobga qarang. Har ikkala paket tiplarga kuchli tayanadi va shuning uchun ularda boshqa import kerak bo'ladi. Shu sababli, agar ilova turiga mos @nestjs/swagger yoki @nestjs/graphql o'rniga @nestjs/mapped-types dan foydalansangiz, turli, hujjatlashtirilmagan yon ta'sirlarga duch kelishingiz mumkin.

Input validatsiya turlarini (DTOlar deb ham ataladi) qurayotganda, ko'pincha bir xil tipning create va update variantlarini yaratish kerak bo'ladi. Masalan, create variantida barcha maydonlar majburiy bo'lishi mumkin, update variantida esa barcha maydonlar ixtiyoriy bo'ladi.

Nest bu vazifani osonlashtirish va boilerplate ni kamaytirish uchun PartialType() yordamchi funksiyasini taqdim etadi.

PartialType() funksiyasi kiruvchi tipdagi barcha xossalari ixtiyoriy bo'lgan yangi tip (klass) qaytaradi. Masalan, bizda quyidagi create tipi bo'lsin:

TypeScript
1export class CreateCatDto {
2  name: string;
3  age: number;
4  breed: string;
5}

Default holatda, bu maydonlarning barchasi majburiy. Xuddi shu maydonlarga ega, lekin har biri ixtiyoriy bo'lgan tip yaratish uchun PartialType() dan foydalanib klass reference (CreateCatDto) ni argument sifatida bering:

TypeScript
1export class UpdateCatDto extends PartialType(CreateCatDto) {}
Hint

PartialType() funksiyasi @nestjs/mapped-types paketidan import qilinadi.

PickType() funksiyasi kiruvchi tipdan xossalar to'plamini tanlab, yangi tip (klass) yaratadi. Masalan, quyidagi tipdan boshlaymiz:

TypeScript
1export class CreateCatDto {
2  name: string;
3  age: number;
4  breed: string;
5}

Ushbu klassdan xossalar to'plamini PickType() yordamchi funksiyasi bilan tanlashimiz mumkin:

TypeScript
1export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}
Hint

PickType() funksiyasi @nestjs/mapped-types paketidan import qilinadi.

OmitType() funksiyasi kiruvchi tipdan barcha xossalarni olib, so'ng muayyan kalitlar to'plamini olib tashlab yangi tip yaratadi. Masalan, quyidagi tipdan boshlaymiz:

TypeScript
1export class CreateCatDto {
2  name: string;
3  age: number;
4  breed: string;
5}

Quyida name dan tashqari barcha xossalarga ega hosila tip yaratamiz. Bu konstruktsiyada OmitType ning ikkinchi argumenti xossa nomlari massividir.

TypeScript
1export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}
Hint

OmitType() funksiyasi @nestjs/mapped-types paketidan import qilinadi.

IntersectionType() funksiyasi ikkita tipni birlashtirib, yangi tip (klass) hosil qiladi. Masalan, quyidagi ikki tipdan boshlaymiz:

TypeScript
1export class CreateCatDto {
2  name: string;
3  breed: string;
4}
5
6export class AdditionalCatInfo {
7  color: string;
8}

Biz barcha xossalarni ikkala tipdan birlashtiradigan yangi tip yaratamiz.

TypeScript
1export class UpdateCatDto extends IntersectionType(
2  CreateCatDto,
3  AdditionalCatInfo,
4) {}
Hint

IntersectionType() funksiyasi @nestjs/mapped-types paketidan import qilinadi.

Type mapping yordamchi funksiyalarini bir-biri bilan kompozitsiya qilish mumkin. Masalan, quyidagisi CreateCatDto tipidagi barcha xossalarga ega bo'lgan, faqat name dan tashqari va bu xossalar ixtiyoriy bo'lgan tipni yaratadi:

TypeScript
1export class UpdateCatDto extends PartialType(
2  OmitType(CreateCatDto, ['name'] as const),
3) {}

Massivlarni parse qilish va validatsiya qilish

TypeScript generics yoki interfeyslar haqida metadata saqlamaydi, shuning uchun ularni DTOlarda ishlatganingizda ValidationPipe kiruvchi ma'lumotlarni to'g'ri validatsiya qila olmasligi mumkin. Masalan, quyidagi kodda createUserDtos to'g'ri validatsiya qilinmaydi:

TypeScript
1@Post()
2createBulk(@Body() createUserDtos: CreateUserDto[]) {
3  return 'This action adds new users';
4}

Massivni validatsiya qilish uchun, massivni o'rab turadigan xossaga ega alohida klass yarating yoki ParseArrayPipe dan foydalaning.

TypeScript
1@Post()
2createBulk(
3  @Body(new ParseArrayPipe({ items: CreateUserDto }))
4  createUserDtos: CreateUserDto[],
5) {
6  return 'This action adds new users';
7}

Bundan tashqari, ParseArrayPipe query parametrlarini parse qilishda ham qo'l keladi. Keling, query parametrlari orqali yuborilgan identifikatorlar bo'yicha userlarni qaytaradigan findByIds() metodini ko'rib chiqamiz.

TypeScript
1@Get()
2findByIds(
3  @Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
4  ids: number[],
5) {
6  return 'This action returns users by ids';
7}

Bu konstruktsiya quyidagi HTTP GET so'rovi orqali kelgan query parametrlarini validatsiya qiladi:

Terminal
1GET /?ids=1,2,3

WebSockets va Microservices

Bu bob HTTP uslubidagi ilovalar (masalan, Express yoki Fastify) misollarini ko'rsatsa-da, ValidationPipe ishlatilayotgan transport usulidan qat'i nazar WebSockets va microservices uchun xuddi shunday ishlaydi.

Batafsil

Custom validatorlar, xato xabarlari va mavjud dekoratorlar haqida batafsil ma'lumotni class-validator paketi taqdim etadi, bu yerda o'qing.