WisarWisar
Dasturlash kitobi/8-QISM — NestJS21 daqiqa

8.15-bob: Caching (Redis), Logger, Error handling

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


1. Kirish va motivatsiya

Config'ni 8.14-bob bildik. Endi production ilovaning ishlash (tezlik), kuzatuv, barqarorlik uchun zarur uchta mavzuni — caching (Redis), logger, error handling — NestJS'da chuqur ko'ramiz. 5.21'da Redis caching'ni, 5.12'da logger'ni, 5.10'da error handling'ni Express'da o'rgandik; endi NestJS'da — CacheModule, structured logger (Pino/Winston), va global exception filter 8.6-bob bilan, professional darajada. Bu uch mavzu — havaskor va production ilovani ajratadi: tez (cache), ko'rinadigan (log), barqaror (xato boshqaruv).

Caching 5.21-bob — tez-tez so'raladigan, kam o'zgaradigan ma'lumotni xotirada saqlash (DB'ga har safar bormaslik). NestJS CacheModule — bir xil kod bilan in-memory yoki Redis (taqsimlangan — 5.21). CacheInterceptor 8.6-bob — javobni avtomatik kesh. Logger 5.12-bob — nima sodir bo'layotganini yozish (debug, monitoring); production'da structured (JSON — Pino/Winston). Error handling 5.10-bob — xatoni markazlashtirilgan ushlash (global exception filter — 8.6), toza javob, log.

Bu bob: CacheModule (Redis), cache interceptor, cache strategiyalar 5.21-bob, Pino/Winston logger, structured logging, global exception filter 8.6-bob, xato turlari — chuqur. Bu uch mavzu 5.21, 5.12, 5.10, 8.6 ni NestJS production darajasiga olib chiqadi. Bu — ilovaning tezligi, ko'rinishi, barqarorligi.

O'xshatish: cache — oshxonadagi tayyor taomlar (5.21: do'kon javoni) — har buyurtmaga noldan pishirmaysiz (DB), tayyorini berasiz (tez). Logger — qora quti (samolyot) — har voqea yoziladi (xato bo'lsa, nima bo'lganini bilasiz). Error handling — xavfsizlik tizimi — nosozlik butun binoni yiqitmaydi (bir xato butun ilovani o'chirmaydi), markazda ushlanadi, log qilinadi, foydalanuvchiga toza xabar. Uchalasi — professional ilovaning poydevori.

Nega muhim?

  • Tezlik — cache (DB yuki kamayadi — 5.21).
  • Ko'rinish — log (xatoni topish, monitoring — 5.12).
  • Barqarorlik — error handling (ilova yiqilmaydi — 5.10).
  • Production — bularsiz jiddiy ilova yo'q.

2. Nazariya — chuqur tushuntirish

2.1. CacheModule sozlash (in-memory)

ts
import { CacheModule } from "@nestjs/cache-manager";

@Module({
  imports: [
    CacheModule.register({                            // in-memory (5.21)
      isGlobal: true,                                 // butun ilovaga
      ttl: 60000,                                     // 60 soniya (ms)
      max: 100,                                       // maks element
    }),
  ],
})
export class AppModule {}

CacheModule: register (in-memory — boshlash uchun), ttl (yashash vaqti — ms), max (maks element). isGlobal (har joyda). Boshlanishiga in-memory, keyin Redisga o'tish — kod o'zgarmaydi 2.2-bob. Bu — 5.21 cache'ning NestJS abstraksiyasi.

2.2. Redis store (taqsimlangan — 5.21)

ts
import { CacheModule } from "@nestjs/cache-manager";
import { redisStore } from "cache-manager-redis-yet";

CacheModule.registerAsync({
  isGlobal: true,
  inject: [ConfigService],
  useFactory: async (config: ConfigService) => ({
    store: await redisStore({                         // Redis (5.21)
      socket: { host: config.get("REDIS_HOST"), port: config.get("REDIS_PORT") },
    }),
    ttl: 60000,
  }),
});

Redis store 5.21-bob: in-memory'dan Redis'ga — faqat store o'zgaradi (kod o'zgarmaydi — abstraksiya foydasi). Redis — taqsimlangan (ko'p instance bir cache — 5.21: 2.3; in-memory har instance alohida). Production'da Redis (gorizontal masshtab — 10). ConfigService bilan 8.14-bob.

2.3. CacheInterceptor (avtomatik kesh)

ts
import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager";

@Controller("products")
@UseInterceptors(CacheInterceptor)                   // avtomatik kesh (8.6)
export class ProductsController {
  @Get()
  @CacheKey("barcha_mahsulot")                       // maxsus kalit
  @CacheTTL(300000)                                  // 5 daqiqa
  hammasi() {
    return this.service.hammasi();                   // natija keshlanadi
  }
}

CacheInterceptor (8.6 interceptor) — GET javobini avtomatik keshlaydi (kalit — route + parametr). @CacheKey (maxsus kalit), @CacheTTL (vaqt). Faqat GET (mutatsiya emas). Eng oson cache (kod kam). Global ham (APP_INTERCEPTOR). Bu — 5.21 cache'ning deklarativ usuli.

2.4. Qo'lda cache (CACHE_MANAGER — nazorat)

ts
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { Cache } from "cache-manager";

@Injectable()
export class ProductsService {
  constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}   // DI

  async bitta(id: string) {
    const kalit = `product_${id}`;
    const keshda = await this.cache.get(kalit);      // cache tekshir (5.21)
    if (keshda) return keshda;                        // cache hit

    const mahsulot = await this.repo.findById(id);   // cache miss  DB
    await this.cache.set(kalit, mahsulot, 300000);   // cache'ga (5 daqiqa)
    return mahsulot;
  }

  async yangila(id: string, dto: any) {
    const natija = await this.repo.update(id, dto);
    await this.cache.del(`product_${id}`);           // o'zgarganda — invalidatsiya (5.21: 2.9)
    return natija;
  }
}

Qo'lda cache (CACHE_MANAGER — to'liq nazorat): cache.get/set/del. Cache-aside naqsh (5.21: 2.5): tekshir yo'q bo'lsa DB cache'ga. Invalidatsiya (o'zgarganda del — 5.21: 2.9 — eng muhim/qiyin). Murakkab cache mantiq uchun (interceptor yetmaganda). Bu — 5.21 strategiyasi.

2.5. Cache strategiyalari va invalidatsiya (5.21)

text
  STRATEGIYALAR 5.21-bob:
  - Cache-aside (lazy): ilova boshqaradi (get/miss/set — 2.4) — eng keng
  - Write-through: yozishda cache + DB birga
  - TTL: vaqt o'tib eskirish (oddiy invalidatsiya)

  INVALIDATSIYA (5.21: 2.9 — eng qiyin):
  - O'zgarganda del (update/delete  cache.del)
  - TTL (avtomatik eskirish)
  - Tag/pattern (bog'liq keshlar)

   "Eng qiyin 2 narsa: cache invalidatsiya va nomlash" (mashhur ibora)

Cache strategiyalar 5.21-bob: cache-aside (eng keng — 2.4), write-through, TTL. Invalidatsiya (5.21: 2.9) — eng qiyin: o'zgarganda eski cache'ni o'chirish (aks holda eski ma'lumot). Usul: del (o'zgarganda), TTL (vaqt). Eski ma'lumot xavfi — invalidatsiya muhim. Nimani keshlash: tez-tez o'qiladi + kam o'zgaradi (5.21: 2.4).

2.6. NestJS Logger (built-in)

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

@Injectable()
export class OrdersService {
  private readonly logger = new Logger(OrdersService.name);   // kontekst nomi

  async yarat(dto: any) {
    this.logger.log(`Buyurtma yaratilmoqda: ${dto.userId}`);   // info
    try {
      const order = await this.repo.save(dto);
      this.logger.log(`Buyurtma yaratildi: ${order.id}`);
      return order;
    } catch (e) {
      this.logger.error(`Buyurtma xatosi: ${e.message}`, e.stack);   // xato + stack
      throw e;
    }
  }
}

NestJS Logger (built-in — 5.12): new Logger(kontekst). Darajalar: log (info), error (stack bilan), warn, debug, verbose. Kontekst nomi (qaysi service). Konsolda rangli (dev). Production uchun structured (JSON — Pino/Winston — 2.7). Bu — 5.12 logger'ning NestJS asosi.

2.6-b. LoggerService interfeysi (custom logger)

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

// NestJS built-in Logger o'rniga o'z logger'ingizni ulash uchun
// LoggerService interfeysini implementatsiya qilasiz (5.12)
@Injectable()
export class MeningLogger implements LoggerService {
  log(message: any, ...optional: any[]) {                // info darajasi
    this.yoz("LOG", message, optional);
  }
  error(message: any, ...optional: any[]) {              // xato + stack
    this.yoz("ERROR", message, optional);
  }
  warn(message: any, ...optional: any[]) {
    this.yoz("WARN", message, optional);
  }
  debug?(message: any, ...optional: any[]) {             // ixtiyoriy (?)
    this.yoz("DEBUG", message, optional);
  }
  verbose?(message: any, ...optional: any[]) {           // ixtiyoriy
    this.yoz("VERBOSE", message, optional);
  }

  private yoz(daraja: string, message: any, meta: any[]) {
    // JSON structured (5.12: 2.6) — o'z formatingiz, o'z transportingiz
    process.stdout.write(JSON.stringify({
      daraja,
      vaqt: new Date().toISOString(),
      xabar: message,
      meta,
    }) + "\n");
  }
}

// main.ts — NestJS'ga o'z logger'ingizni bering
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(new MeningLogger());                       // built-in o'rniga

LoggerService interfeysi 5.12-bob: NestJS built-in Loggerni o'z logger'ingiz bilan almashtirish uchun LoggerService interfeysini implementatsiya qilasiz (log/error/warn/debug?/verbose?? — ixtiyoriy metodlar). app.useLogger(...) — butun ilova (framework ichki loglari ham) shu logger'dan o'tadi. bufferLogs: true — logger tayyor bo'lguncha loglarni bufferga oladi (dastlabki loglar yo'qolmaydi). Amaliyotda o'zingiz yozmaysiz — Pino/Winston adapterini ulaysiz 2.7-bob, lekin interfeysni bilish muhim (moslashuv).

2.7. Pino/Winston (structured logging — 5.12)

ts
// nestjs-pino (tez, structured JSON)
import { LoggerModule } from "nestjs-pino";

LoggerModule.forRoot({
  pinoHttp: {
    level: process.env.NODE_ENV === "production" ? "info" : "debug",
    transport: process.env.NODE_ENV !== "production"
      ? { target: "pino-pretty" }                    // dev: chiroyli rangli
      : undefined,                                    // prod: JSON (5.12: 2.6)
    redact: ["req.headers.authorization", "*.parol"],   // sirlarni yashirish (14)
  },
});

Pino/Winston (5.12 — structured): production'da JSON log (parse qilinadi — monitoring, ELK/Grafana — 5.12: 2.6). nestjs-pino (tez, har log'da request kontekst). pino-pretty (dev — rangli). redactsirlarni yashirish (parol, token log'ga tushmasin — 14 — muhim!). Pino — tez (async — event loop bloklanmaydi). Bu — 5.12'ning production amaliyoti.

Winston alternativasi (nest-winston) — Pino'dan boyroq konfiguratsiya (ko'p transport: fayl, konsol, tashqi servis — 5.12), lekin biroz sekinroq:

ts
// nest-winston (moslashuvchan transportlar — 5.12)
import { WinstonModule } from "nest-winston";
import * as winston from "winston";

WinstonModule.forRoot({
  level: process.env.NODE_ENV === "production" ? "info" : "debug",
  format: winston.format.combine(                      // strukturaviy format
    winston.format.timestamp(),
    winston.format.json(),                             // JSON (prod — 2.6)
  ),
  transports: [
    new winston.transports.Console(),                  // konsol
    new winston.transports.File({                      // faylga (rotatsiya bilan)
      filename: "logs/error.log",
      level: "error",                                  // faqat xatolar
    }),
    new winston.transports.File({ filename: "logs/combined.log" }),
  ],
});
// main.ts: app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));

Pino vs Winston 5.12-bob: Pino — tezlik ustuvor (yuqori trafik, async, minimal ortiqcha yuk) — ko'pincha birinchi tanlov. Winston — moslashuvchanlik ustuvor (ko'p transport: fayl rotatsiyasi, Elasticsearch, Slack; boy format). Ikkalasi ham useLogger orqali NestJS built-in Logger'ni almashtiradi — service kodingizdagi this.logger.log(...) o'zgarmaydi (abstraksiya). Odatiy tanlov: Pino (agar maxsus transport zarur bo'lmasa).

2.8. Log darajalari va nima loglash (5.12)

text
  DARAJALAR 5.12-bob:
  error — xatolar (darrov e'tibor)
  warn  — ogohlantirish (potentsial muammo)
  info  — muhim hodisa (login, buyurtma, to'lov)
  debug — batafsil (faqat dev)

  NIMA LOGLASH:
   So'rov (method, path, status, vaqt) — interceptor 8.6-bob
   Muhim biznes hodisa (to'lov, ro'yxat)
   Xatolar (stack bilan)
   Sirlar (parol, token, karta — redact! 14)
   Har mayda narsa (shovqin — muhimni yashiradi)

Log darajalari 5.12-bob: error/warn/info/debug. Nima loglash: so'rov (interceptor — 8.6), muhim hodisa (to'lov), xato (stack). Sirlarni loglama (parol/token — redact — 14). Mayda shovqin loglama (muhimni ko'mib qo'yadi). Production'da info+, dev'da debug. To'g'ri log — muammoni tez topish.

2.9. Global Exception Filter (8.6 — markaziy xato)

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

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

    const status = exception instanceof HttpException
      ? exception.getStatus() : 500;                  // HTTP yoki 500
    const message = exception instanceof HttpException
      ? exception.getResponse() : "Server xatosi";

    this.logger.error(`${request.method} ${request.url} ${status}`,   // log (5.12)
      (exception as Error).stack);

    response.status(status).json({                    // toza javob (5.10)
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

Global Exception Filter (8.6, 5.10) — barcha xatoni markazda ushlaydi (@Catch()). HTTP xato status; boshqa 500. Log (stack — 5.12) + toza javob (foydalanuvchiga — 5.10: 2.7). Bu — xato boshqaruvning yuragi: ilova yiqilmaydi, xato ko'rinadi, javob izchil. APP_FILTER (global — 8.6: 2.12).

2.9-b. HttpAdapterHost (platforma-agnostik javob)

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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  // HttpAdapterHost — DI orqali (Express/Fastify farqini yashiradi)
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost) {
    // response.json() emas — adapter orqali (platformadan mustaqil)
    const { httpAdapter } = this.httpAdapterHost;       // to'g'ri adapter
    const ctx = host.switchToHttp();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    const javob = {                                      // izchil format (5.10)
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()), // adapter — URL
      message: exception instanceof HttpException
        ? exception.getResponse()
        : "Server xatosi",
    };

    // Express'da response.status().json(), Fastify'da reply.code().send() —
    // adapter avtomatik to'g'ri metodni chaqiradi
    httpAdapter.reply(ctx.getResponse(), javob, status);
  }
}

HttpAdapterHost 8.6-bob — NestJS ikki HTTP platformada ishlaydi: Express (odatiy) va Fastify (tezroq). Ularning response obyekti farq qiladi (res.json() vs reply.send()). Agar filterda to'g'ridan-to'g'ri response.status().json() yozsangiz — kod faqat Express'ga bog'lanadi. HttpAdapterHost DI orqali to'g'ri adapterni beradi (httpAdapter.reply(...), getRequestUrl(...)) — kod platformadan mustaqil bo'ladi. Muhim nuqta: exception filter juda erta (ilova to'liq ishga tushmasdan) ishlashi mumkin, shu bois response obyektini @Inject(HttpAdapterHost) orqali olish — rasmiy tavsiya etilgan AllExceptionsFilter naqshi (kutubxona xatolari ham to'g'ri ushlanadi).

2.10. Xato turlari va HTTP status (8.1, 5.10)

ts
// NestJS built-in HTTP exception'lar (8.1)
throw new BadRequestException("Noto'g'ri ma'lumot");     // 400
throw new UnauthorizedException("Kirish kerak");         // 401
throw new ForbiddenException("Ruxsat yo'q");             // 403
throw new NotFoundException("Topilmadi");                // 404
throw new ConflictException("Email band");               // 409
throw new InternalServerErrorException();                // 500

// Custom exception
export class InsufficientStockException extends HttpException {
  constructor(mahsulot: string) {
    super(`${mahsulot}: zaxira yetarli emas`, HttpStatus.BAD_REQUEST);
  }
}

Xato turlari (8.1, 5.10): NestJS built-in exception'lar (to'g'ri HTTP status — 400/401/403/404/409/500). Custom exception (domenga xos — InsufficientStock). To'g'ri status — RESTful 5.7-bob, frontend to'g'ri ishlov beradi. Exception filter 2.9-bob bularni ushlaydi. Operatsion vs dasturchi xatosi (5.10: 2.3).

2.11. Logging interceptor (so'rov kuzatuvi — 8.6)

ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private logger = new Logger("HTTP");
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const boshlanish = Date.now();
    return next.handle().pipe(
      tap(() => {                                      // javobdan keyin (8.6)
        const vaqt = Date.now() - boshlanish;
        this.logger.log(`${req.method} ${req.url}${vaqt}ms`);   // so'rov + vaqt
      }),
    );
  }
}
// APP_INTERCEPTOR — global (har so'rov loglanadi — 8.6: 2.12)

Logging interceptor 8.6-bob — har so'rovni loglaydi (method, URL, vaqt — sekin endpoint topish). tap (RxJS — javobdan keyin — 8.6: 2.6). Global (APP_INTERCEPTOR). Bu — monitoring asosi (so'rov kuzatuvi). Pino bilan avtomatik 2.7-bob.

2.12. Cache + tezlik (production)

text
  Cache foydasi 5.21-bob:
  DB so'rov: 50ms  cache: 1ms (50x tez)
  DB yuki: kamayadi (ko'p o'qish cache'dan)

  Nimani keshlash:
   Mahsulot katalogi (kam o'zgaradi, ko'p o'qiladi)
   Konfiguratsiya, statik ma'lumot
   Og'ir hisoblash natijasi (aggregation — 8.13)
   Tez o'zgaradigan (balans, real-time)
   Foydalanuvchiga xos maxfiy (ehtiyot — 14)

Cache tezligi 5.21-bob: DB (50ms) cache (1ms). DB yuki kamayadi (masshtab — 10). Keshlash: kam o'zgaradi + ko'p o'qiladi (katalog, aggregation natija). Keshlamaslik: tez o'zgaradigan (balans), maxfiy (14). To'g'ri cache — sezilarli tezlik (production muhim).

2.13. Health check va monitoring (10 ko'prik)

ts
// @nestjs/terminus — health check
@Controller("health")
export class HealthController {
  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck("database"),            // DB ulanish
      () => this.redis.checkHealth("redis"),          // Redis
    ]);
  }
}

Health check (@nestjs/terminus) — ilova "sog'lig'i" (DB, Redis ulanganmi). /health endpoint — monitoring/load balancer tekshiradi (10 — Kubernetes liveness/readiness). Production'da majburiy (avtomatik qayta ishga tushirish). Log + health + metrics — to'liq kuzatuv (observability).

2.14. Best practices (cache/log/error)

text
  CACHE:
   Redis production (taqsimlangan — 2.2); to'g'ri narsani keshlash 2.12-bob
   Invalidatsiya (o'zgarganda del — 2.5); TTL

  LOGGER:
   Structured JSON prod (Pino/Winston — 2.7); kontekst 2.6-bob
   Sirlarni redact (parol/token — 14, 2.7); to'g'ri daraja 2.8-bob
   So'rov interceptor (vaqt — 2.11)

  ERROR:
   Global exception filter (markaziy — 2.9); toza javob 5.10-bob
   To'g'ri HTTP status 2.10-bob; custom exception (domen)
   Xato log (stack — 5.12); ilova yiqilmaydi
   Health check (monitoring — 2.13)

3. Sintaksis — tez ma'lumotnoma

ts
// Cache (2.1, 2.4)
CacheModule.registerAsync({ useFactory: () => ({ store: await redisStore({...}) }) })
@UseInterceptors(CacheInterceptor) @CacheKey("x") @CacheTTL(300000)
@Inject(CACHE_MANAGER) cache: Cache    cache.get/set/del

// Logger (2.6, 2.7)
private logger = new Logger(Context.name)    logger.log/error/warn/debug
class MyLogger implements LoggerService { log() {} error() {} }   // custom (2.6-b)
app.useLogger(new MyLogger())   // yoki Pino/Winston adapter
LoggerModule.forRoot({ pinoHttp: { redact: [...] } })   // Pino structured

// Error (2.9, 2.10)
@Catch() class Filter implements ExceptionFilter { catch(ex, host) {} }
constructor(private httpAdapterHost: HttpAdapterHost) {}   // platforma-agnostik (2.9-b)
throw new NotFoundException() / BadRequestException() / ...

4. Batafsil kod namunalari

Misol 1 — Redis cache setup (2.2)

ts
// app.module.ts
import { CacheModule } from "@nestjs/cache-manager";
import { redisStore } from "cache-manager-redis-yet";

@Module({
  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      inject: [ConfigService],
      useFactory: async (config: ConfigService) => ({
        store: await redisStore({
          socket: {
            host: config.get("REDIS_HOST"),
            port: config.get<number>("REDIS_PORT"),
          },
          ttl: 60000,
        }),
      }),
    }),
  ],
})
export class AppModule {}

Misol 2 — Cache-aside service (2.4)

ts
@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product) private repo: Repository<Product>,
    @Inject(CACHE_MANAGER) private cache: Cache,
  ) {}

  async bitta(id: number): Promise<Product> {
    const kalit = `product:${id}`;
    const keshda = await this.cache.get<Product>(kalit);
    if (keshda) return keshda;                         // cache hit (tez — 2.12)

    const mahsulot = await this.repo.findOneBy({ id });
    if (!mahsulot) throw new NotFoundException("Topilmadi");
    await this.cache.set(kalit, mahsulot, 300000);    // 5 daqiqa
    return mahsulot;
  }

  async hammasi(): Promise<Product[]> {
    const keshda = await this.cache.get<Product[]>("products:all");
    if (keshda) return keshda;
    const mahsulotlar = await this.repo.find();
    await this.cache.set("products:all", mahsulotlar, 60000);
    return mahsulotlar;
  }

  async yangila(id: number, dto: UpdateProductDto) {
    const natija = await this.repo.update(id, dto);
    // Invalidatsiya (2.5 — eng muhim)
    await this.cache.del(`product:${id}`);
    await this.cache.del("products:all");
    return natija;
  }

  async ochir(id: number) {
    await this.repo.delete(id);
    await this.cache.del(`product:${id}`);
    await this.cache.del("products:all");
  }
}

Misol 3 — CacheInterceptor (avtomatik — 2.3)

ts
@Controller("products")
export class ProductsController {
  @Get()
  @UseInterceptors(CacheInterceptor)                  // avtomatik kesh
  @CacheKey("products_all")
  @CacheTTL(60000)
  hammasi() {
    return this.service.hammasi();
  }

  // Mutatsiya — kesh yo'q (interceptor faqat GET)
  @Post()
  yarat(@Body() dto: CreateProductDto) {
    return this.service.yarat(dto);                   // service'da invalidatsiya (Misol 2)
  }
}

Misol 4 — Pino logger setup (2.7)

ts
// app.module.ts
import { LoggerModule } from "nestjs-pino";

@Module({
  imports: [
    LoggerModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        pinoHttp: {
          level: config.get("app.env") === "production" ? "info" : "debug",
          transport: config.get("app.env") !== "production"
            ? { target: "pino-pretty", options: { colorize: true } }   // dev
            : undefined,                               // prod: JSON
          redact: {                                    // sirlarni yashirish (14)
            paths: ["req.headers.authorization", "req.body.parol", "req.body.token"],
            censor: "***",
          },
          customProps: (req) => ({ userId: (req as any).user?.id }),   // har log'da user
        },
      }),
    }),
  ],
})
export class AppModule {}

// main.ts
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));                       // Pino'ni NestJS logger qilish

Misol 5 — Global exception filter (to'liq — 2.9)

ts
// filters/global-exception.filter.ts
import { Catch, ExceptionFilter, ArgumentsHost, HttpException, HttpStatus, Logger } from "@nestjs/common";

@Catch()
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 = "Server xatosi yuz berdi";

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      message = exception.getResponse();
    }

    // Log 5.12-bob — server xato (500) batafsil
    if (status >= 500) {
      this.logger.error(
        `${request.method} ${request.url} ${status}`,
        (exception as Error).stack,
      );
    } else {
      this.logger.warn(`${request.method} ${request.url} ${status}: ${JSON.stringify(message)}`);
    }

    // Toza javob (5.10 — ichki tafsilot oshkor qilinmaydi — 14)
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: typeof message === "string" ? message : (message as any).message,
    });
  }
}

// main.ts (global)
app.useGlobalFilters(new GlobalExceptionFilter());
// yoki APP_FILTER (DI bilan — 8.6: 2.12)

Misol 6 — Logging interceptor (so'rov vaqti — 2.11)

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

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

    return next.handle().pipe(
      tap(() => {
        const vaqt = Date.now() - boshlanish;
        const status = context.switchToHttp().getResponse().statusCode;
        const daraja = vaqt > 1000 ? "warn" : "log";   // sekin  warn
        this.logger[daraja](`${method} ${url} ${status}${vaqt}ms`);
      }),
      catchError((err) => {
        const vaqt = Date.now() - boshlanish;
        this.logger.error(`${method} ${url}${vaqt}ms — ${err.message}`);
        throw err;
      }),
    );
  }
}

Misol 7 — Custom exception'lar (domen — 2.10)

ts
// exceptions/insufficient-stock.exception.ts
export class InsufficientStockException extends HttpException {
  constructor(mahsulot: string, mavjud: number, kerak: number) {
    super(
      {
        statusCode: HttpStatus.BAD_REQUEST,
        error: "InsufficientStock",
        message: `${mahsulot}: faqat ${mavjud} ta mavjud (${kerak} ta so'raldi)`,
      },
      HttpStatus.BAD_REQUEST,
    );
  }
}

// Ishlatish (service)
if (mahsulot.zaxira < kerak) {
  throw new InsufficientStockException(mahsulot.nom, mahsulot.zaxira, kerak);
}

Misol 8 — Cache decorator (qayta ishlatish — kengaytma)

ts
// Cacheable custom decorator (DRY)
export function Cacheable(kalitPrefix: string, ttl = 300000) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = async function (...args: any[]) {
      const cache: Cache = this.cache;
      const kalit = `${kalitPrefix}:${JSON.stringify(args)}`;
      const keshda = await cache.get(kalit);
      if (keshda) return keshda;
      const natija = await original.apply(this, args);
      await cache.set(kalit, natija, ttl);
      return natija;
    };
  };
}
// @Cacheable("user") async bitta(id) {...}

Misol 9 — Redis to'g'ridan (cache emas — sessiya/navbat)

ts
// Redis'ni to'g'ridan ishlatish (cache'dan tashqari — session, rate limit, pub/sub)
import { InjectRedis } from "@nestjs-modules/ioredis";
import Redis from "ioredis";

@Injectable()
export class RateLimitService {
  constructor(@InjectRedis() private redis: Redis) {}

  async tekshir(userId: string): Promise<boolean> {
    const kalit = `ratelimit:${userId}`;
    const soni = await this.redis.incr(kalit);        // atomik oshirish
    if (soni === 1) await this.redis.expire(kalit, 60);   // 60 soniya oyna
    return soni <= 10;                                 // 1 daqiqada 10
  }
}
// Redis — cache + session (8.12 bot) + rate limit + pub/sub (5.13)

Misol 10 — To'liq production setup (2.14)

ts
// main.ts — production observability
async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  const config = app.get(ConfigService);

  app.useLogger(app.get(Logger));                     // Pino (Misol 4)
  app.useGlobalFilters(new GlobalExceptionFilter()); // markaziy xato (Misol 5)
  app.useGlobalInterceptors(new LoggingInterceptor());   // so'rov log (Misol 6)
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));   // (8.5)

  app.enableShutdownHooks();                          // graceful shutdown (8.1)
  await app.listen(config.get("app.port"));
}
bootstrap();
//  cache (tez) + log (ko'rinish) + error filter (barqaror) + health = production

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

1) Cache invalidatsiyasiz

text
 o'zgargandan keyin eski cache (eski ma'lumot — 2.5)
 update/delete'da cache.del

2) Sirlarni loglash

text
 parol/token log'da (14)
 redact (Pino — 2.7)

3) Xato ushlanmaydi (ilova yiqiladi)

text
 filter yo'q (xato  500 + stack oshkor — 5.10)
 global exception filter (toza javob + log — 2.9)

4) Hammani keshlash

text
 tez o'zgaradigan/maxfiy kesh (eski/xavf — 2.12)
 kam o'zgaradi + ko'p o'qiladi

5) console.log production'da

text
 console.log (structured emas, sekin — 5.12)
 Logger / Pino (JSON, daraja)

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Cache ishlamaydi (har safar DB)

Sababi: kalit har xil, yoki TTL 0 2.4-bob. Yechimi: kalit izchil; TTL to'g'ri.

Xato 2 — Eski ma'lumot (cache stale)

Sababi: invalidatsiya yo'q 2.5-bob. Yechimi: o'zgarganda del.

Xato 3 — Redis ulanmaydi

Sababi: host/port noto'g'ri, Redis ishlamayapti 5.21-bob. Yechimi: Redis ishga tushir; config; Docker.

Xato 4 — Log production'da ko'rinmaydi

Sababi: daraja noto'g'ri, yoki useLogger yo'q 2.7-bob. Yechimi: level: info; app.useLogger.

Xato 5 — Stack trace foydalanuvchiga ketadi

Sababi: exception filter yo'q (2.9, 14). Yechimi: global filter (toza javob).

Xato 6 — CacheInterceptor mutatsiyani keshlaydi

Sababi: POST'ga interceptor 2.3-bob. Yechimi: faqat GET'ga.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Caching 5.21-bob: Redis — CacheModule.
  • Logger 5.12-bob: Pino/Winston.
  • Error handling 5.10-bob: exception filter.
  • Interceptor/Filter 8.6-bob: cache, logging, exception.
  • Redis 8.12-bob: session, rate limit, pub/sub.
  • Config 8.14-bob: Redis/log sozlama.
  • DevOps (10): monitoring, health, masshtab.
  • Throttler 8.16-bob: rate limit (Redis).

8. Eng yaxshi amaliyotlar (best practices)

  • Redis cache (taqsimlangan — prod — 2.2); to'g'ri narsani keshlash 2.12-bob.
  • Cache invalidatsiya (o'zgarganda del — 2.5); TTL.
  • Structured JSON log (Pino/Winston — prod — 2.7); kontekst 2.6-bob.
  • Sirlarni redact (parol/token — 14, 2.7); to'g'ri daraja 2.8-bob.
  • So'rov interceptor (vaqt — sekin topish — 2.11).
  • Global exception filter (markaziy, toza javob — 2.9).
  • To'g'ri HTTP status (built-in + custom — 2.10).
  • Xato log (stack); ilova yiqilmaydi 2.9-bob.
  • Health check (monitoring — 2.13); graceful shutdown.
  • console.log emas (Logger — 5.12).

9. Amaliy loyiha: "Production-Tayyor Backend (Cache + Log + Error)"

NestJS production poydevorini mustahkamlash.

Maqsad

Ilovaga cache (Redis), structured log (Pino), markaziy error handling qo'shib, production-tayyor qilish.

Talablar (requirements)

  1. Redis cache: CacheModule.registerAsync (Misol 1, 2.2).
  2. Cache-aside: get/set/del + invalidatsiya (Misol 2, 2.4, 2.5).
  3. CacheInterceptor: GET avtomatik kesh (Misol 3, 2.3).
  4. Pino logger: structured, redact, muhitga bog'liq (Misol 4, 2.7).
  5. Logging interceptor: so'rov + vaqt (Misol 6, 2.11).
  6. Global exception filter: markaziy, toza javob, log (Misol 5, 2.9).
  7. Custom exception: domen xatosi (Misol 7, 2.10).
  8. Redis to'g'ridan: rate limit (Misol 9).
  9. Health check: DB/Redis 2.13-bob.
  10. Production setup: main.ts (cache+log+filter+graceful — Misol 10, 2.14).

Maslahatlar (hint)

  • Cache invalidatsiya (del — 2.5, 2-xato).
  • Redact sirlar (14, 2-holat).
  • Exception filter toza javob (2.9, 5-xato).
  • To'g'ri narsani kesh (2.12, 4-holat).
  • CacheInterceptor faqat GET (2.3, 6-xato).
  • Logger (console.log emas — 5.12, 5-holat).

"Tayyor" mezonlari (acceptance criteria)

  • Redis cache.
  • Cache-aside + invalidatsiya.
  • CacheInterceptor.
  • Pino logger (redact).
  • Logging interceptor.
  • Global exception filter.
  • Custom exception.
  • Redis rate limit.
  • Health check.
  • Production main.ts.

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


10. Xulosa va keyingi bobga ko'prik

Bu bobda production poydevorining uch ustunini chuqur o'rgandik:

  • Caching: CacheModule (in-memory Redis — 2.1, 2.2); CacheInterceptor (avtomatik — 2.3); qo'lda cache-aside + invalidatsiya (2.4, 2.5); tezlik 2.12-bob.
  • Logger: NestJS Logger 2.6-bob; Pino/Winston (structured JSON, redact — 2.7); darajalar 2.8-bob; logging interceptor 2.11-bob.
  • Error handling: global exception filter (markaziy — 2.9); xato turlari + status 2.10-bob; custom exception; health check 2.13-bob.

Keyingi bob — 8.16-bob: Microservices (NestJS mikroservislar). Production poydevorini bildik; endi NestJS'ning ilg'or arxitektura mavzusiga — mikroservislar ga — o'tamiz: monolit vs mikroservis, transport (TCP/Redis/RabbitMQ/Kafka), message/event pattern, API Gateway. Bu — katta, masshtablanadigan tizimlar arxitekturasi (9, 10 bilan bog'liq).


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com/techniques/caching (CacheModule, CacheInterceptor, Redis store)
  • docs.nestjs.com/techniques/logger (built-in Logger, LoggerService, useLogger)
  • github.com/iamolegga/nestjs-pino (structured logging, redact, request context)
  • github.com/gremo/nest-winston (Winston integratsiya, transportlar)
  • docs.nestjs.com/exception-filters (global exception filter, HttpException, HttpAdapterHost)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.15-bob: Caching (Redis), Logger, Error handling — Wisar