NestJS

Middleware, Guard, Interceptor, Exception filter

NestJS'da so'rov controller'ga yetib borishidan oldin va javob mijozga qaytishdan oldin bir necha bosqichdan o'tadi. Bu bosqichlar โ€” Middleware, Guard, Pipe, Interceptor va Exception filter. Har biri o'z vazifasiga ega. Ushbu yakuniy darsda ushbu quvurni (pipeline) va uning har bir bo'g'inini o'rganamiz.

So'rovni qayta ishlash quvuri

Avval umumiy manzarani ko'ramiz. So'rov kelganda u quyidagi tartibda o'tadi. Bu tartibni bilish qaysi vositani qayerda ishlatishni tushunish uchun muhim:

  1. Middleware โ€” eng birinchi, xom so'rov ustida (Express middleware kabi);
  2. Guard โ€” so'rovni o'tkazish yoki rad etish to'g'risida qaror qabul qiladi (masalan, autentifikatsiya);
  3. Interceptor (kirish) โ€” controller'gacha qo'shimcha logika;
  4. Pipe โ€” parametrlar va tanani o'zgartirish/tekshirish (masalan, ValidationPipe);
  5. Controller โ€” asosiy ish;
  6. Interceptor (chiqish) โ€” javobni o'zgartirish;
  7. Exception filter โ€” biror joyda xato yuz bersa, uni tutadi.
Har bir bo'g'inning aniq vazifasi bor. Umumiy qoida: Guard โ€” "kirishga ruxsat bormi?", Pipe โ€” "ma'lumot to'g'rimi?", Interceptor โ€” "kirish/chiqishni o'zgartiraymi?", Exception filter โ€” "xatoni qanday ko'rsataymi?".

Middleware

Middleware Express'dagi middleware bilan bir xil g'oyaga ega: u so'rov controller'ga yetishidan oldin ishlaydigan funksiya. Odatda logging, so'rovni tayyorlash yoki umumiy tekshiruvlar uchun ishlatiladi. NestJS'da middleware klass ko'rinishida yoziladi:

// src/logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(req.method + ' ' + req.originalUrl);
    next();   // Keyingi bosqichga o'tkazish (Express'dagidek)
  }
}

Middleware'ni modulda ro'yxatdan o'tkazamiz โ€” modul NestModule'ni amalga oshirib, configure metodida uni qaysi marshrutlarga qo'llashni belgilaydi:

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');   // Barcha marshrutlarga qo'llash
  }
}

Guard โ€” autentifikatsiya va avtorizatsiya

Guard bitta savolga javob beradi: "Bu so'rovga ruxsat bermi?". U true qaytarsa, so'rov davom etadi; false qaytarsa yoki istisno tashlasa, NestJS 403 Forbidden qaytaradi. Guard'lar autentifikatsiya (kimligini aniqlash) va avtorizatsiya (huquqini tekshirish) uchun ideal.

Guard CanActivate interfeysini amalga oshiradi. canActivate metodi true/false yoki ularni beruvchi Promise qaytaradi:

// src/auth/auth.guard.ts
import {
  Injectable, CanActivate, ExecutionContext, UnauthorizedException,
} from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    // So'rov obyektini olamiz:
    const request = context.switchToHttp().getRequest();
    const token = request.headers['authorization'];

    if (!token) {
      throw new UnauthorizedException('Token topilmadi');
    }

    // Bu yerda token haqiqiyligini tekshirish logikasi bo'ladi
    // (masalan, JWT'ni tekshirish)
    return true;   // Ruxsat berilsa
  }
}

Guard'ni marshrut yoki controller'ga @UseGuards dekoratori orqali qo'llaymiz:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';

@Controller('profil')
export class ProfilController {

  @Get()
  @UseGuards(AuthGuard)   // Bu marshrut himoyalangan
  getProfil() {
    return 'Maxfiy profil ma'lumoti';
  }
}
@UseGuards'ni metodga qo'ysangiz โ€” faqat o'sha metod himoyalanadi. Controller klassiga qo'ysangiz โ€” undagi barcha metodlar himoyalanadi. Global qilish uchun esa app.useGlobalGuards()'dan foydalaning.

Pipe โ€” o'zgartirish va tekshirish

Pipeni oldingi darsda ko'rgan edik (ValidationPipe, ParseIntPipe). Uning ikki vazifasi bor: o'zgartirish (masalan, satrni songa) va tekshirish (masalan, DTO validatsiyasi). O'zingizning maxsus pipe'ingizni ham yozishingiz mumkin:

// src/pipes/bosh-harf.pipe.ts
import { PipeTransform, Injectable } from '@nestjs/common';

@Injectable()
export class BoshHarfPipe implements PipeTransform {
  transform(value: string): string {
    // Kelgan satrni bosh harflarga o'giradi:
    return value.toUpperCase();
  }
}

Bu pipe'ni parametrga qo'llaganda kelgan qiymat controller'ga yetguncha o'zgaradi:

@Get(':nom')
findByName(@Param('nom', BoshHarfPipe) nom: string) {
  // Agar /ali so'ralsa, nom = 'ALI'
  return nom;
}

Interceptor โ€” kirish va chiqishni boyitish

Interceptor so'rov oldida ham, javob keyin ham logika qo'shishga imkon beradi. U ayniqsa quyidagilar uchun foydali: barcha javoblarni yagona formatga o'rash, so'rov davomiyligini o'lchash (logging), javobni kesh qilish. Interceptor RxJS'ning Observable'idan foydalanadi:

// src/interceptors/transform.interceptor.ts
import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      // Har bir javobni yagona formatga o'raymiz:
      map(data => ({ muvaffaqiyat: true, malumot: data })),
    );
  }
}

Endi controller [{...}] qaytarsa ham, mijoz shu ko'rinishda javob oladi:

// Controller qaytargan: [{ id: 1, ism: 'Ali' }]
// Mijoz oladigan javob:
// {
//   "muvaffaqiyat": true,
//   "malumot": [{ "id": 1, "ism": "Ali" }]
// }

Interceptor'ni ham @UseInterceptors dekoratori bilan qo'llaymiz:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { TransformInterceptor } from '../interceptors/transform.interceptor';

@Controller('users')
@UseInterceptors(TransformInterceptor)   // Barcha metodlarga
export class UsersController {
  @Get()
  findAll() {
    return [{ id: 1, ism: 'Ali' }];
  }
}

Exception filter โ€” xatolarni boshqarish

Exception filter ilovada yuz bergan xatolarni tutadi va mijozga qanday ko'rsatishni nazorat qiladi. NestJS'da tayyor istisnolar mavjud: NotFoundException (404), BadRequestException (400), UnauthorizedException (401), ForbiddenException (403). Ularni shunchaki tashlaysiz:

import { NotFoundException, BadRequestException } from '@nestjs/common';

// Service ichida:
if (!user) {
  throw new NotFoundException('Foydalanuvchi topilmadi');
}
if (yosh < 18) {
  throw new BadRequestException('Yosh 18 dan katta bo'lsin');
}

Xatoni butun ilova bo'ylab yagona formatda ko'rsatish uchun maxsus filter yozamiz. U ExceptionFilter'ni amalga oshiradi va @Catch dekoratori qaysi xatoni tutishni belgilaydi:

// src/filters/http-exception.filter.ts
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException,
} from '@nestjs/common';
import { Response } from 'express';

@Catch(HttpException)   // Barcha HTTP istisnolarini tutamiz
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();

    // Yagona xato formati:
    response.status(status).json({
      muvaffaqiyat: false,
      kod: status,
      xabar: exception.message,
      vaqt: new Date().toISOString(),
    });
  }
}

Filter'ni @UseFilters bilan yoki global tarzda qo'llash mumkin:

// Global qo'llash (main.ts):
app.useGlobalFilters(new HttpExceptionFilter());

// Yoki bitta controller/metodga:
// @UseFilters(HttpExceptionFilter)
Exception filter faqat xatoni ko'rsatish uchun. Xatoni oldini olish uchun validatsiya (Pipe) va tekshiruvlar (Guard) ishlating. Filter โ€” bu oxirgi himoya qatlami: kutilmagan xato yuz berganda ham mijozga chiroyli va bir xil javob berish uchun.

Barchasini birlashtirish

Amalda bu vositalar birgalikda qo'llaniladi. Quyidagi controller himoyalangan, validatsiyalangan va yagona javob formatiga ega:

import {
  Controller, Post, Body, UseGuards, UseInterceptors, UseFilters,
} from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { TransformInterceptor } from '../interceptors/transform.interceptor';
import { HttpExceptionFilter } from '../filters/http-exception.filter';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
@UseGuards(AuthGuard)                    // Autentifikatsiya
@UseInterceptors(TransformInterceptor)   // Javob formati
@UseFilters(HttpExceptionFilter)         // Xato formati
export class UsersController {

  @Post()
  create(@Body() dto: CreateUserDto) {   // Pipe DTO'ni tekshiradi
    return { id: 1, ism: dto.ism };
  }
}
E'tibor bering: bitta controller ustida to'rt xil qatlam ishlaydi โ€” Guard ruxsatni, Pipe ma'lumotni, Interceptor javobni, Filter xatoni boshqaradi. Har biri mustaqil, qayta ishlatiladigan va testlanadigan. Bu NestJS arxitekturasining kuchi โ€” murakkab logika kichik, aniq bo'laklarga bo'linadi.

Xulosa