WisarWisar
Dasturlash kitobi/8-QISM — NestJS22 daqiqa

8.6-bob: Guards, Interceptors, Middleware, Exception Filters (chuqur)

8-QISM — NestJS (chuqur) · 6-mavzu


1. Kirish va motivatsiya

DTO/validatsiyani 8.5-bob bildik. Endi NestJS'ning eng kuchli, "sehrli" mexanizmlariga — request lifecycle (8.1: 2.12) qatlamlariga — o'tamiz: Middleware, Guards, Interceptors, Exception Filters (+ Pipes — 8.5). Bular — cross-cutting concerns (ko'ndalang masalalar): auth, logging, kesh, xato — har endpoint'da takrorlanadigan ishlarni bir joyda, deklarativ hal qiladi. Bu — NestJS'ni Express'dan keskin ajratadigan, professional kodning asosi.

Express'da auth, log, xato — har joyda qo'lda middleware (5.6, 5.10). NestJS — bularni maxsus, tashkillangan qatlamlarga ajratadi: Guard (kirishga ruxsatmi — auth/RBAC — 5.15, 5.17), Interceptor (so'rov/javobni o'rab olish — log, transform, kesh, timeout), Middleware (eng past daraja — so'rov oldidan), Exception Filter (xatoni ushlash — 5.10). Har biri request lifecycleda aniq o'rinda ishlaydi (8.1: 2.12). Bu — toza, qayta ishlatiladigan, deklarativ kod.

Eng muhimi: request lifecycle tartibini tushunish (8.1: 2.12 — bu yerda chuqur). So'rov: Middleware Guard Interceptor (oldin) Pipe Handler Interceptor (keyin) Filter (xato bo'lsa). Qaysi vazifa qaysi qatlamga tegishli — bu bilim NestJS'ni "bilaman" deyishning belgisi. Bu bob: to'rt mexanizm (guard, interceptor, middleware, filter — pipe 8.5'da), RxJS interceptor'da, ExecutionContext, custom decorator, va tartib — chuqur. Bu — auth 8.9-bob, RBAC 8.7-bob, log, xato — hammasining asosi.

O'xshatish (request lifecycle): so'rov — aeroportdagi yo'lovchi. Middleware — kirish eshigi (umumiy tekshiruv — log). Guard — pasport nazorati (kirishga ruxsatmi — auth — 5.15). Pipe — bagaj tekshiruvi (ma'lumot to'g'rimi — validatsiya — 8.5). Handler — samolyot (asosiy ish). Interceptor — xizmat (uchishdan oldin/keyin — ovqat, e'lon — log/transform). Exception Filter — favqulodda xizmat (muammo bo'lsa — xato boshqaruvi). Har biri aniq o'rinda, aniq vazifa.

Nega muhim?

  • Cross-cutting — auth/log/xato bir joyda (takrorlamasdan).
  • Request lifecycle — NestJS'ni tushunishning kaliti.
  • Auth/RBAC asosi — guard (8.7, 8.9).
  • Professional kod — deklarativ, toza, qayta ishlatiladigan.

2. Nazariya — chuqur tushuntirish

2.1. Request lifecycle (to'liq tartib — 8.1: 2.12 chuqur)

text
  HTTP so'rov 
  1. MIDDLEWARE          (umumiy — log, header)
  2. GUARDS              (ruxsatmi — auth/RBAC — 5.15, 5.17)
  3. INTERCEPTORS (oldin) (so'rovni o'rab — log boshlash, kesh tekshir)
  4. PIPES               (validatsiya/transform — 8.5)
  5. CONTROLLER HANDLER  (route metod  service)
  6. INTERCEPTORS (keyin) (javobni o'rab — transform, log tugatish)
  7. EXCEPTION FILTERS   (xato bo'lsa — istalgan bosqichda)
   HTTP javob

Request lifecycle tartibi — eng muhim tushuncha: har qatlam aniq o'rinda. Guard (3) pipe (4) dan oldin auth validatsiyadan oldin (ruxsatsiz — validatsiyaga ham bormaydi). Interceptor handler'ni o'raydi (oldin + keyin). Filter — istalgan joyda xato. Bu tartib — qaysi vazifa qayerda.

2.2. Qaysi mexanizm qaysi vazifa (tanlov)

text
  ┌─────────────┬──────────────────────────────────────┐
  │ Mexanizm    │ Vazifa                               │
  ├─────────────┼──────────────────────────────────────┤
  │ Middleware  │ umumiy (log, header, body parse)     │
  │ Guard       │ RUXSAT (auth, RBAC — true/false)     │
  │ Interceptor │ o'rash (transform, log, kesh, timeout)│
  │ Pipe 8.5-bob  │ validatsiya/transform (ma'lumot)     │
  │ Exception Filter│ XATO (ushlash, javob)            │
  └─────────────┴──────────────────────────────────────┘

Tanlov: Guard — "kirsa bo'ladimi?" (ruxsat — true/false). Interceptor — "so'rov/javobni o'zgartir/kuzat" (log, transform, kesh). Middleware — eng umumiy (past daraja). Pipe — ma'lumot 8.5-bob. Filter — xato. Har vazifaga to'g'ri mexanizm (auth guard, log interceptor).

2.3. Middleware (eng past daraja)

Middleware — so'rov controller'ga yetmasdan oldin ishlaydi (Express middleware — 5.6 — docs):

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.url}`);   // log (5.12)
    next();                                      // keyingi (5.6)
  }
}

// Modulda ulash
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes("*");   // barcha route
  }
}

Middleware — Express middleware 5.6-bob ning NestJS versiyasi (NestMiddleware, use). Eng past daraja (req/res to'g'ridan). Umumiy ish (log, header, raw body). configure da ulanadi (modulda). Lekin middleware ExecutionContext'ni bilmaydi (qaysi controller/handler — guard/interceptor biladi). Auth guard afzal 2.4-bob.

Middleware ikki ko'rinishda yoziladi — class (yuqorida, NestMiddleware) va funksional (oddiy funksiya — holatsiz, DI shart bo'lmaganda):

ts
// Funksional middleware (holatsiz — DI kerak bo'lmasa — eng yengil)
import { Request, Response, NextFunction } from "express";

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`${req.method} ${req.url}`);
  next();                                        // keyingi 5.6-bob — chaqirilmasa so'rov osiladi 
}

Class vs funksional: class middleware — @Injectable(), DI ishlaydi (service inject qilish mumkin — masalan config, log service). Funksional middleware — oddiy funksiya, DI yo'q, lekin yengil (holatsiz ish uchun). Docs tavsiyasi: DI kerak bo'lmasa — funksional (soddaroq).

MiddlewareConsumer — moslashuvchan ulash. configure metodida middleware'ni qaysi route'larga ulashni aniq boshqarasiz:

ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from "@nestjs/common";

@Module({ /* ... */ })
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware, logger)           // bir nechta middleware (tartib bilan)
      .exclude(                                   // istisno route'lar (middleware o'tkazib yuboradi)
        { path: "auth/login", method: RequestMethod.POST },
        "health",                                 // path string ham mumkin
      )
      .forRoutes(
        { path: "users", method: RequestMethod.GET },   // faqat GET /users
        UsersController,                          // yoki butun controller
      );
  }
}

apply().forRoutes() — asosiy shakl. forRoutes qabul qiladi: string ("users"), { path, method } obyekti (aniq metod), yoki controller class (uning barcha route'lari). "*" — hamma route (wildcard). exclude — istisno route'lar (masalan /health, /login — auth log kerak emas). apply ga bir nechta middleware berilsa — ketma-ket (berilgan tartibda) ishlaydi.

Global middleware — modul configure'siz, main.ts'da to'g'ridan (Express app.use — hamma route):

ts
// main.ts — global middleware (barcha route, MiddlewareConsumer'siz)
import { logger } from "./logger.middleware";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(logger);                               // funksional middleware — global
  await app.listen(3000);
}

app.use() — faqat funksional middleware qabul qiladi (class emas — DI konteynerdan o'tmaydi). DI kerak global middleware uchun — modulda forRoutes("*") ishlating. app.use — Express bilan bir xil (raw body, third-party middleware — helmet, cookie-parser — 5.6).

2.4. Guard (ruxsat — auth/RBAC)

Guard — so'rovga ruxsat beradi yoki rad etadi (CanActivate — docs):

ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {   // true — ruxsat; false — 403
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization;
    if (!token) return false;                          // ruxsat yo'q  403
    // ... token tekshir 5.15-bob ...
    return true;                                        // ruxsat
  }
}

// Ishlatish (dekorator)
@UseGuards(AuthGuard)
@Get("profile")
profile() {}

GuardCanActivate interface (canActivateboolean/Promise<boolean>). true ruxsat (handler ishlaydi); false 403 (avtomatik). ExecutionContext 2.7-bob — qaysi handler ishlashini biladi (metama'lumot — RBAC). Auth 8.9-bob, RBAC 8.7-bob — guard bilan. Pipe'dan oldin (ruxsatsiz — validatsiyaga ham bormaydi — 2.1).

2.5. Interceptor (o'rash — transform/log/kesh)

Interceptor — handler'ni o'rab oladi (oldin + keyin — RxJS — docs):

ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";
import { tap, map } from "rxjs/operators";

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const boshlandi = Date.now();
    console.log("So'rov boshlandi");               // OLDIN (handler'dan oldin)

    return next.handle().pipe(                       // handler'ni chaqir
      tap(() => console.log(`Vaqt: ${Date.now() - boshlandi}ms`)),   // KEYIN
    );
  }
}

InterceptorNestInterceptor (intercept — RxJS Observable). next.handle() — handler'ni chaqiradi; uning oldin (kodda before) va keyin (.pipe — RxJS) ishlash mumkin. Transform (javobni o'zgartirish), log (timing), kesh, timeout. RxJS 2.6-bob — interceptor'ning asosi.

2.6. RxJS interceptor'da (Observable, map/tap)

ts
// Javobni o'rash (standart format — 5.7)
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => ({ success: true, data })),    // javobni o'rab (5.7)
    );
  }
}
// Handler { id: 1 } qaytarsa  { success: true, data: { id: 1 } }

RxJS (interceptor'da): next.handle()Observable (handler natijasi). map — javobni o'zgartirish (standart format — 5.7); tap — yon ta'sir (log — javobni o'zgartirmasdan); catchError — xato; timeout — vaqt cheklash. NestJS RxJS ishlatadi (FRP — 8.1). Asosiy operatorlar yetadi (chuqur RxJS shart emas).

2.7. ExecutionContext (qaysi handler — metadata)

ExecutionContext — qaysi controller/handler ishlashini va metama'lumotni biladi:

ts
import { Reflector } from "@nestjs/core";

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}     // DI (8.2)

  canActivate(context: ExecutionContext): boolean {
    // Metama'lumotni o'qish (@Roles dekoratoridan — 2.9)
    const rollar = this.reflector.get<string[]>("roles", context.getHandler());
    if (!rollar) return true;                        // rol talab qilinmagan
    const { user } = context.switchToHttp().getRequest();
    return rollar.includes(user.rol);                // RBAC (5.17)
  }
}

ExecutionContext — guard/interceptor'ga kontekst beradi: switchToHttp().getRequest() (req), getHandler() (qaysi metod), getClass() (controller). Reflector (DI) — @SetMetadata/custom dekorator 2.9-bob metama'lumotini o'qiydi (RBAC rollari). Bu — guard'ni dinamik qiladi (handler'ga qarab).

2.8. Exception Filter (xato ushlash — 5.10)

Exception Filter — xatoni ushlab, javob qaytaradi (5.10 — docs):

ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from "@nestjs/common";

@Catch()                                            // barcha xato (yoki @Catch(HttpException))
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception instanceof HttpException
      ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({                   // izchil xato javob (5.7, 5.10)
      success: false,
      statusCode: status,
      message: exception instanceof HttpException ? exception.message : "Server xatosi",
    });
  }
}

Exception FilterExceptionFilter (catch), @Catch() (qaysi xato). NestJS built-in filter bor (HttpException avtomatik JSON — 8.1: 2.6). Custom filter — izchil format 5.7-bob, log 5.12-bob, xatolarni moslash (5.10: Zod/DB status). Bu — 5.10 (global error handler) ning NestJS versiyasi.

HttpException iyerarxiyasi. NestJS'da barcha HTTP xatolar bitta bazaviy HttpException klassidan meros oladi — har biri tayyor status kodi bilan:

ts
import {
  HttpException, BadRequestException, UnauthorizedException,
  ForbiddenException, NotFoundException, ConflictException,
  InternalServerErrorException, HttpStatus,
} from "@nestjs/common";

throw new BadRequestException("Noto'g'ri so'rov");        // 400
throw new UnauthorizedException("Token kerak");           // 401
throw new ForbiddenException("Ruxsat yo'q");              // 403
throw new NotFoundException("Topilmadi");                 // 404
throw new ConflictException("Allaqachon mavjud");         // 409
throw new InternalServerErrorException();                 // 500

// Bazaviy — ixtiyoriy status
throw new HttpException("Maxsus xato", HttpStatus.I_AM_A_TEAPOT);  // 418

HttpException daraxti: HttpException (baza) BadRequestException (400), UnauthorizedException (401), ForbiddenException (403), NotFoundException (404), ConflictException (409), UnprocessableEntityException (422), InternalServerErrorException (500) va h.k. Bularni throw qilsangiz — NestJS built-in filter avtomatik to'g'ri status + JSON qaytaradi 8.1-bob. Filter ichida exception instanceof HttpException bilan aynan shu iyerarxiyani tekshirasiz (Misol 7).

Custom exception — biznesga xos xato (baza klassini kengaytirib):

ts
// domain xatosi — o'z status va payload'i bilan
export class InsufficientBalanceException extends HttpException {
  constructor(kerakli: number, mavjud: number) {
    super(
      { xato: "Mablag' yetarli emas", kerakli, mavjud },   // response body (obyekt ham mumkin)
      HttpStatus.PAYMENT_REQUIRED,                          // 402
    );
  }
}

throw new InsufficientBalanceException(100, 30);
// Javob: 402 { xato: "Mablag' yetarli emas", kerakli: 100, mavjud: 30 }

Custom exceptionHttpException'ni extends qilib, super(response, status) chaqiring. response — string yoki obyekt (izchil payload). Bu — biznes xatosini 5.10-bob toza, tipli, qayta ishlatiladigan qiladi. Filter ularni ham avtomatik ushlaydi (meros — instanceof HttpException).

@Catch(...) — nishonli filter. @Catch() (parametrsiz) — hamma xato; @Catch(HttpException) — faqat HTTP xatolar; @Catch(PrismaClientKnownRequestError) — faqat Prisma DB xatolari (5.10 — DB xatoni statusga moslash):

ts
import { Catch, ArgumentsHost, ExceptionFilter, ConflictException } from "@nestjs/common";
import { Prisma } from "@prisma/client";

@Catch(Prisma.PrismaClientKnownRequestError)               // faqat Prisma xatolari
export class PrismaExceptionFilter implements ExceptionFilter {
  catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    if (exception.code === "P2002") {                       // unique constraint (5.10)
      return response.status(409).json({ success: false, message: "Allaqachon mavjud" });
    }
    return response.status(500).json({ success: false, message: "DB xatosi" });
  }
}

Nishonli @Catch: bitta filter bir necha aniq xato turini ham ushlaydi (@Catch(A, B)). Bir nechta filter bo'lsa — NestJS eng aniq mos filterni tanlaydi (Prisma xatosi PrismaFilter, qolgani global @Catch()). Bu — DB/tashqi xatolarni HTTP statusga toza moslash yo'li 5.10-bob.

2.9. Custom decorator + metadata (RBAC asosi)

ts
import { SetMetadata } from "@nestjs/common";

// Custom dekorator (@Roles — 5.17, 7.6)
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);

// Ishlatish
@Roles("admin")                                     // metama'lumot qo'yadi
@UseGuards(RolesGuard)                              // guard o'qiydi (2.7)
@Delete(":id")
ochir() {}

// Custom param decorator (@CurrentUser — 7.6)
export const CurrentUser = createParamDecorator(
  (data, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user,
);
@Get("me")
me(@CurrentUser() user: User) { return user; }      // req.user (8.9)

Custom decorator 7.6-bob + metadata: SetMetadata — handler'ga ma'lumot biriktiradi (@Roles("admin")); guard Reflector bilan o'qiydi 2.7-bob — RBAC 8.7-bob. createParamDecorator — parametr dekorator (@CurrentUser() — req.user — 8.9). Bu — NestJS'ning deklarativ kuchi (auth/RBAC toza).

2.10. Bog'lash darajalari (global, controller, route)

ts
// Global (main.ts — hamma)
app.useGlobalGuards(new AuthGuard());
app.useGlobalInterceptors(new LoggingInterceptor());
app.useGlobalFilters(new GlobalExceptionFilter());

// Controller (butun controller)
@UseGuards(AuthGuard)
@Controller("users")
export class UsersController {}

// Route (bitta metod)
@UseGuards(RolesGuard)
@Delete(":id")
ochir() {}

Uch daraja: global (main.ts — hamma endpoint); controller (@UseGuards class'da — butun controller); route (metod'da — bitta). Tartib: global controller route 2.11-bob. DI kerak bo'lsa — global'ni provider sifatida (APP_GUARD — 2.12).

2.11. Bajarilish tartibi (bir nechta)

text
  Guard'lar (bir nechta): global  controller  route (bog'lash tartibida)
  Interceptor'lar: oldin — globalcontrollerroute; keyin — TESKARI (FILO)
   Interceptor "matryoshka" (RxJS — first in, last out — 2.5)

Tartib: guard'lar — bog'lash tartibida (global birinchi). Interceptor'lar — oldin (tashqaridan ichkariga), keyin (ichkaridan tashqariga — FILO — RxJS). Bir nechta bo'lsa muhim (masalan auth guard roles guard).

2.12. DI bilan global (APP_GUARD)

ts
// Global guard DI bilan (provider sifatida — 8.2)
import { APP_GUARD, APP_INTERCEPTOR, APP_FILTER } from "@nestjs/core";

@Module({
  providers: [
    { provide: APP_GUARD, useClass: AuthGuard },            // global guard (DI ishlaydi)
    { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
    { provide: APP_FILTER, useClass: GlobalExceptionFilter },
  ],
})
export class AppModule {}

APP_GUARD/APP_INTERCEPTOR/APP_FILTER (provider sifatida) — global + DI ishlaydi (new AuthGuard() — DI yo'q; provider — DI bor). Guard'ga service inject kerak bo'lsa (JwtService — 8.9) — shu usul. Tavsiya (best practices): global enhancer'larni provider sifatida.

2.13. Real use-case'lar

text
  Guard:        auth (JWT — 8.9), RBAC (5.17, 8.7), rate limit, feature flag
  Interceptor:  javob formati 5.7-bob, logging/timing 5.12-bob, kesh 5.21-bob,
                timeout, ClassSerializer (parolsiz — 8.5: 2.13)
  Middleware:   raw body, correlation ID 5.12-bob, CORS (lekin built-in afzal)
  Filter:       global xato 5.10-bob, Prisma/TypeORM xato  status, log

2.14. Guard vs Middleware (qachon qaysi)

text
  Middleware: umumiy, ExecutionContext'siz (log, header)
  Guard: AUTH/RUXSAT — ExecutionContext bilan (qaysi handler, metadata — RBAC)

   Auth uchun GUARD (middleware emas):
     - ExecutionContext (handler/metadata — RBAC)
     - true/false (toza ruxsat mantig'i)
     - DI (JwtService — 8.9)
     - Pipe'dan oldin (validatsiyadan oldin ruxsat)

Auth Guard (middleware emas): Express'da auth middleware edi 5.15-bob; NestJS'da — guard (ExecutionContext, metadata, true/false, DI). Middleware — faqat umumiy (log). Bu — NestJS'ning to'g'ri yo'li (8.9 auth — guard bilan).

2.15. Best practices (14)

text
   Auth/RBAC — guard (middleware emas — 2.14)
   Javob format/log/kesh — interceptor 2.5-bob
   Global enhancer — APP_GUARD provider (DI — 2.12)
   Custom decorator + metadata (@Roles — RBAC — 2.9)
   Global exception filter (izchil xato — 5.10, 2.8)
   Request lifecycle tartibini biling (guardpipehandler — 2.1)
   ExecutionContext (handler/metadata — 2.7)
   ClassSerializerInterceptor (parolsiz javob — 8.5)

3. Sintaksis — tez ma'lumotnoma

ts
// Guard (2.4)
@Injectable() class AuthGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean { return true; }
}
@UseGuards(AuthGuard)

// Interceptor (2.5)
@Injectable() class LogInterceptor implements NestInterceptor {
  intercept(ctx, next: CallHandler) { return next.handle().pipe(map(d => ...)); }
}
@UseInterceptors(LogInterceptor)

// Middleware 2.3-bob: NestMiddleware, use(req, res, next)
// Filter 2.8-bob: @Catch() class implements ExceptionFilter { catch(exc, host) {} }
// Custom decorator 2.9-bob: SetMetadata("key", value); createParamDecorator
// Global DI 2.12-bob: { provide: APP_GUARD, useClass: X }

4. Batafsil kod namunalari

Misol 1 — Auth Guard (JWT — 8.9 kirish — 2.4)

ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";           // (8.9)

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}    // DI (8.2)

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();   // (2.7)
    const token = request.headers.authorization?.split(" ")[1];   // "Bearer X" (5.15)
    if (!token) throw new UnauthorizedException("Token kerak");    // 401 (8.1)

    try {
      const payload = await this.jwtService.verifyAsync(token);    // (8.9)
      request.user = payload;                        // req.user (8.9)
      return true;                                    // ruxsat
    } catch {
      throw new UnauthorizedException("Token noto'g'ri");
    }
  }
}

Misol 2 — Roles Guard (RBAC — 5.17, 8.7 — 2.7, 2.9)

ts
// roles.decorator.ts (custom decorator — 2.9)
import { SetMetadata } from "@nestjs/common";
export const ROLES_KEY = "roles";
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}      // DI (2.7)

  canActivate(context: ExecutionContext): boolean {
    const kerakliRollar = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),                           // route metadata
      context.getClass(),                             // controller metadata
    ]);
    if (!kerakliRollar) return true;                  // rol talab qilinmagan
    const { user } = context.switchToHttp().getRequest();
    if (!kerakliRollar.includes(user.rol)) {
      throw new ForbiddenException("Ruxsat yo'q");     // 403 (5.17)
    }
    return true;
  }
}

// Ishlatish (auth + roles birga)
@UseGuards(AuthGuard, RolesGuard)                    // tartib: auth  roles (2.11)
@Roles("admin")
@Delete(":id")
ochir(@Param("id") id: string) {}

Misol 3 — Transform Interceptor (javob format — 5.7 — 2.6)

ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<T, any> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => ({                                // standart format (5.7)
        success: true,
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}
// Har handler javobi avtomatik o'raladi: { success: true, data: ..., timestamp }

Misol 4 — Logging Interceptor (timing — 5.12 — 2.5)

ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from "@nestjs/common";
import { tap } from "rxjs/operators";

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private logger = new Logger("HTTP");               // NestJS logger (8.15)

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const { method, url } = req;
    const boshlandi = Date.now();

    return next.handle().pipe(
      tap(() => {                                      // KEYIN (javobni o'zgartirmasdan — 2.6)
        this.logger.log(`${method} ${url}${Date.now() - boshlandi}ms`);   // (5.12)
      }),
    );
  }
}

Misol 5 — Timeout Interceptor (2.5, 2.6)

ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from "@nestjs/common";
import { Observable, throwError, TimeoutError } from "rxjs";
import { timeout, catchError } from "rxjs/operators";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),                                  // 5 sekund (RxJS)
      catchError((err) => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());   // 408
        }
        return throwError(() => err);
      }),
    );
  }
}

Misol 6 — Cache Interceptor (5.21 — 2.5)

ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { of, Observable } from "rxjs";
import { tap } from "rxjs/operators";

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  constructor(private cache: RedisService) {}        // DI (5.21)

  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const req = context.switchToHttp().getRequest();
    const kalit = `cache:${req.url}`;
    const keshlangan = await this.cache.get(kalit);

    if (keshlangan) return of(JSON.parse(keshlangan));   // cache hit 5.21-bob — handler ishlamaydi

    return next.handle().pipe(
      tap((data) => this.cache.set(kalit, JSON.stringify(data), 60)),   // keshla
    );
  }
}
//  Production'da @nestjs/cache-manager CacheInterceptor (8.15)

Misol 7 — Global Exception Filter (5.10 — 2.8)

ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from "@nestjs/common";

@Catch()                                             // barcha xato
export class GlobalExceptionFilter implements ExceptionFilter {
  private logger = new Logger("Exception");

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message: any = "Ichki server xatosi";

    if (exception instanceof HttpException) {         // NestJS xato (8.1)
      status = exception.getStatus();
      message = exception.getResponse();
    }

    // 5xx — to'liq log 5.12-bob; 4xx — qisqa (5.10)
    if (status >= 500) {
      this.logger.error(`${request.method} ${request.url}`, (exception as Error).stack);
    }

    response.status(status).json({                   // izchil format (5.7)
      success: false,
      statusCode: status,
      message,
      path: request.url,
      timestamp: new Date().toISOString(),
    });
  }
}

Misol 8 — Custom param decorator (@CurrentUser — 2.9)

ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

// req.user'ni olish (auth guard qo'ygan — Misol 1)
export const CurrentUser = createParamDecorator(
  (data: keyof User | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return data ? user?.[data] : user;               // @CurrentUser("id") yoki @CurrentUser()
  },
);

// Ishlatish (toza — req.user o'rniga)
@UseGuards(AuthGuard)
@Get("me")
me(@CurrentUser() user: User) {                      // to'liq user
  return user;
}
@Get("my-id")
myId(@CurrentUser("id") userId: number) {            // faqat id
  return { userId };
}

Misol 9 — Global enhancer'lar (DI — 2.12)

ts
// app.module.ts — global enhancer'lar (DI ishlaydi — 2.12)
import { APP_GUARD, APP_INTERCEPTOR, APP_FILTER } from "@nestjs/core";

@Module({
  providers: [
    { provide: APP_GUARD, useClass: AuthGuard },              // global auth (DI — JwtService)
    { provide: APP_INTERCEPTOR, useClass: ResponseInterceptor }, // global javob format
    { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },  // global log
    { provide: APP_FILTER, useClass: GlobalExceptionFilter },    // global xato
  ],
})
export class AppModule {}
//  Public endpoint uchun @Public() dekorator + guard'da tekshirish (2.9)

Misol 10 — @Public dekorator (global guard istisno — 2.9)

ts
// public.decorator.ts
export const IS_PUBLIC_KEY = "isPublic";
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

// AuthGuard — public endpoint'larni o'tkazib yuborish
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private jwtService: JwtService, private reflector: Reflector) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(), context.getClass(),
    ]);
    if (isPublic) return true;                        // public — token shart emas

    // ... token tekshir (Misol 1) ...
    return true;
  }
}

// Ishlatish — global auth, lekin login/signup public
@Public()
@Post("login")
login(@Body() dto: LoginDto) {}                      // token shart emas (global guard o'tkazadi)

5. To'g'ri va noto'g'ri holatlar

1) Auth uchun middleware (guard o'rniga)

text
 auth middleware (ExecutionContext yo'q — 2.14)
 auth guard (metadata, DI, true/false)

2) Global guard new bilan (DI kerak bo'lsa)

ts
//  DI ishlamaydi (JwtService inject bo'lmaydi)
app.useGlobalGuards(new AuthGuard());

//  APP_GUARD provider (DI — 2.12)
{ provide: APP_GUARD, useClass: AuthGuard }

3) Interceptor'da Observable qaytarmaslik

ts
//  next.handle() qaytarilmadi (handler ishlamaydi — 2.5)
intercept(ctx, next) { next.handle(); }

//  return + pipe
intercept(ctx, next) { return next.handle().pipe(map(...)); }

4) Xatoni har controller'da qo'lda

text
 har joyda try/catch + format (takror — 5.10)
 global exception filter (bir joyda — 2.8)

5) req.user'ni to'g'ridan (custom decorator o'rniga)

ts
//  req.user (tursiz, takror)
@Req() req   req.user

//  @CurrentUser (toza, tipli — 2.9)
@CurrentUser() user: User

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Guard DI ishlamaydi (global)

Sababi: new Guard() — DI yo'q 2.12-bob. Yechimi: APP_GUARD provider.

Xato 2 — Interceptor javobni o'zgartirmaydi

Sababi: next.handle() qaytarilmagan yoki pipe yo'q 2.5-bob. Yechimi: return next.handle().pipe(map(...)).

Xato 3 — Reflector metadata undefined

Sababi: custom dekorator (@Roles) qo'yilmagan, yoki getHandler/getClass 2.7-bob. Yechimi: dekorator qo'shing; getAllAndOverride (handler + class).

Xato 4 — Global guard public endpoint'ni bloklaydi

Sababi: @Public istisnosi yo'q 2.9-bob. Yechimi: @Public dekorator + guard'da tekshirish (Misol 10).

Xato 5 — Exception filter ishlamaydi

Sababi: @Catch noto'g'ri, yoki global ulanmagan 2.8-bob. Yechimi: @Catch(); APP_FILTER yoki useGlobalFilters.

Xato 6 — Guard tartibi noto'g'ri (roles auth'dan oldin)

Sababi: @UseGuards tartibi 2.11-bob. Yechimi: auth birinchi (@UseGuards(AuthGuard, RolesGuard)).


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Request lifecycle (8.1: 2.12): qatlamlar tartibi.
  • Pipe 8.5-bob: validatsiya (shu oilada).
  • Auth (8.9, 5.15): AuthGuard (JWT).
  • RBAC (8.7, 5.17): RolesGuard + @Roles.
  • Error handling 5.10-bob: exception filter.
  • Logger 5.12-bob: logging interceptor.
  • Kesh (5.21, 8.15): cache interceptor.
  • Decorators 7.6-bob: custom dekorator, metadata.
  • DI 8.2-bob: APP_GUARD, Reflector.
  • RxJS: interceptor (map/tap).

8. Eng yaxshi amaliyotlar (best practices)

  • Auth/RBAC — guard (middleware emas — ExecutionContext/DI — 2.14).
  • Javob format/log/kesh — interceptor (RxJS — 2.5).
  • Global enhancer — APP_GUARD provider (DI — 2.12).
  • Custom decorator + metadata (@Roles/@Public/@CurrentUser — 2.9).
  • Global exception filter (izchil xato — 5.10, 2.8).
  • Request lifecycle tartibini biling (guardpipehandler — 2.1).
  • ExecutionContext (handler/metadata — Reflector — 2.7).
  • Guard tartibi (auth roles — 2.11).
  • @Public (global guard istisno — 2.9, Misol 10).
  • ClassSerializerInterceptor (parolsiz javob — 8.5).

9. Amaliy loyiha: "Cross-Cutting Qatlam (Guard/Interceptor/Filter)"

NestJS enhancer'larni mustahkamlash.

Maqsad

To'liq cross-cutting qatlamni qurish: auth/roles guard, transform/log interceptor, global exception filter, custom decorator.

Talablar (requirements)

  1. Auth Guard: JWT tekshirish; req.user (Misol 1, 2.4).
  2. Roles Guard + @Roles: RBAC; Reflector metadata (Misol 2, 2.7, 2.9).
  3. Transform Interceptor: standart javob format (Misol 3, 2.6).
  4. Logging Interceptor: timing (Misol 4, 2.5).
  5. Exception Filter: global, izchil xato, log (Misol 7, 2.8).
  6. Custom param decorator: @CurrentUser (Misol 8, 2.9).
  7. @Public: global guard istisno (Misol 10, 2.9).
  8. Global enhancer'lar: APP_GUARD/INTERCEPTOR/FILTER (DI — Misol 9, 2.12).
  9. Tartib: auth roles; request lifecycle (2.1, 2.11).
  10. (Bonus) Timeout/Cache interceptor (Misol 5, 6).

Maslahatlar (hint)

  • Auth guard (middleware emas — 2.14, 1-xato).
  • Global DI APP_GUARD provider (2.12, 1-xato).
  • Interceptor: return next.handle().pipe (2.5, 3-xato).
  • Metadata: Reflector.getAllAndOverride (2.7, 3-xato).
  • @Public + guard tekshiruvi (2.9, 4-xato).
  • Guard tartibi: auth birinchi (2.11, 6-xato).

"Tayyor" mezonlari (acceptance criteria)

  • Auth Guard (JWT, req.user).
  • Roles Guard + @Roles (RBAC).
  • Transform Interceptor (format).
  • Logging Interceptor (timing).
  • Exception Filter (global, log).
  • @CurrentUser decorator.
  • @Public (istisno).
  • Global enhancer'lar (DI).
  • Tartib to'g'ri (authroles).
  • (Bonus) Timeout/Cache.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS'ning cross-cutting mexanizmlarini chuqur o'rgandik:

  • Request lifecycle (Middleware Guard Interceptor Pipe Handler Interceptor Filter — 2.1); qaysi mexanizm qaysi vazifa 2.2-bob.
  • Middleware (umumiy — 2.3); Guard (ruxsat — CanActivate — 2.4); Interceptor (o'rash — RxJS map/tap — 2.5, 2.6); Exception Filter (xato — 2.8).
  • ExecutionContext (handler/metadata — Reflector — 2.7); custom decorator (@Roles/@CurrentUser/@Public — 2.9).
  • Bog'lash (global/controller/route — 2.10); tartib 2.11-bob; APP_GUARD (DI — 2.12); guard vs middleware 2.14-bob.

Keyingi bob — 8.7-bob: User–Role–Auth, RBAC (ruxsatlarni boshqarish). Guard/interceptor mexanizmini bildik; endi ularni ishlatib, NestJS'da to'liq RBAC (rol asosida ruxsat) tizimini quramiz: user-role-permission, RolesGuard, dinamik ruxsatlar, ownership. Bu — 5.17 (RBAC) ning NestJS'dagi to'liq amaliyoti.


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com — Guards, Interceptors, Middleware, Exception filters, Custom decorators
  • docs.nestjs.com/faq/request-lifecycle — to'liq tartib; Reflector, ExecutionContext
  • rxjs.dev — Observable, operatorlar (map, tap, catchError, timeout)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.6-bob: Guards, Interceptors, Middleware, Exception Filters (chuqur) — Wisar