IlmHamroh
JavaScript Full-stack/30-qism. NestJS24/65-dars17 daqiqa
Mundarija (26)

Exception filter'lar NestJS'da: HttpException, @Catch, ArgumentsHost va Problem Details

Qisqacha: Handler, pipe, guard yoki interceptor xato tashlasa, uni exception filter (xato filtri) ushlaydi va javobga aylantiradi. Nest'ning o'z filtri HttpException ni (NotFoundException, ConflictException…) { message, error, statusCode } ga aylantiradi, qolgan har qanday xatoni — 500 Internal server error ga. O'z filtringiz — @Catch(XatoKlassi) + ExceptionFilter.catch(xato, host); host.switchToHttp() — req va res. @Catch() argumentsiz — hamma xato. Eng yaqin filtr ushlaydi: metod → controller → global. NestJS 12 da HttpException ga errorCode qo'shildi. vazifalar-api ning Problem Details filtri global — APP_FILTER bilan, DI orqali logger oladi.

Bu darsda

  • Nest'ning o'rnatilgan HttpException oilasi qanday javob berishini haqiqiy chiqishda ko'rasiz.
  • @Catch, ExceptionFilter, ArgumentsHost bilan o'z filtringizni yozasiz.
  • Filtrlar qaysi tartibda tanlanishini va BaseExceptionFilter dan qanday meros olishni bilasiz.
  • ORM xatolarini (Prisma, pg) qaysi qatlamda HTTP xatoga aylantirish kerakligini tushuntira olasiz.
  • vazifalar-api ning Problem Details filtrini APP_FILTER ga o'tkazasiz.

Oldin bilishingiz kerak: Xato javoblari: Problem Details, Xato turlari va custom error iyerarxiyasi, Global error handler, Interceptor'lar va RxJS.

O'tgan to'rt darsda so'rov hayotining har qatlamida xato tashladik. Hammasi bitta joyga keladi — filtrga:

Qatlam Bizdagi misol Status Kim javobga aylantiradi
guard (himoyachi) Content-Type: text/plain 415 filtr
interceptor (ushlab qoluvchi) timeout(100) 503 filtr
pipe Zod sxemasi rad etdi 422 filtr
handler / xizmat vazifa topilmadi 404 filtr
istalgan joy kutilmagan TypeError 500 filtr

Oxirgi ustun hamma qatorda bir xil. Xato qayerda tug'ilmasin, mijozga javobni bitta qatlam — exception filter (xato filtri) — yozadi. Shuning uchun javob shakli ham bitta bo'la oladi.

1. Nega bu kerak?

25-qismda Express'ning global xato ishlovchisini yozgan edik (Global error handler): (err, req, res, next) to'rt argumentli middleware. U har xatoni Problem Details javobiga aylantiradi: kutilgan xato (IlovaXatosi) — o'z statusi bilan, kutilmagani — 500 va tafsilotsiz.

Nest'da shu ishni filtr qiladi. Bootstrap darsida ProblemDetailsFilter ni app.useGlobalFilters(new ProblemDetailsFilter(...)) bilan uladik — ishlaydi. Lekin ikki savol qoldi:

  • Nest'ning o'z xatolari (NotFoundException va boshqalar) qanday ko'rinishda? Kutubxonalar ularni tashlaydi — bizning filtr ularni to'g'ri aylantiradimi?
  • Filtr logger'ni qanday oladi? Hozir u constructor argumenti — new bilan qo'lda beriladi; DI'dan emas.

O'xshatish: kasalxonaning qabul bo'limi. Qaysi bo'limda nima bo'lmasin (jarrohlik, terapiya, laboratoriya), bemor qabulga keladi va u yerda bir xil shakldagi hujjat oladi. Filtr — qabul bo'limi: xato qayerda tug'ilgani emas, mijozga bir xil javob muhim.

2. HttpException oilasi

2.1 Tayyor xatolar

Nest'da har HTTP xato statusi uchun klass bor: BadRequestException (400), UnauthorizedException (401), ForbiddenException (403), NotFoundException (404), ConflictException (409), UnprocessableEntityException (422), ServiceUnavailableException (503) va boshqalar. Hammasi HttpException dan meros oladi.

Demo controller'da ularni tashlab, sukut filtr javobini yozib oldik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Chiqishda har qatorning tana shakliga qarang — qaysi maydonlar bor, qaysisi yo'q:

ts
@Get('a') a() { throw new NotFoundException(); }
@Get('b') b() { throw new NotFoundException("Vazifa 7 yo'q"); }
@Get('c') c() {
  throw new ConflictException('Takror vazifa', {
    errorCode: 'TAKROR_MATN',
  });
}
@Get('d') d() {
  throw new BadRequestException(["matn bo'sh", 'limit > 100']);
}
@Get('e') e() {
  throw new HttpException(
    { xabar: "o'zimizning tana", kod: 42 }, HttpStatus.I_AM_A_TEAPOT,
  );
}
@Get('f') f() { throw new TypeError('x is undefined'); }
text
GET /a → 404 {"message":"Not Found","statusCode":404}
GET /b → 404 {"message":"Vazifa 7 yo'q","error":"Not Found","statusCode":404}
GET /c → 409 {"message":"Takror vazifa","error":"Conflict","statusCode":409,"errorCode":"TAKROR_MATN"}
GET /d → 400 {"message":["matn bo'sh","limit > 100"],"error":"Bad Request","statusCode":400}
GET /e → 418 {"xabar":"o'zimizning tana","kod":42}
GET /f → 500 {"statusCode":500,"message":"Internal server error"}

(Bu demoda global filtr har xatoni logga ham yozgan — o'sha qatorlarni tushirib qoldirdik.) Olti holatdan olti saboq:

  • /a — xabarsiz: message — status nomi, error maydoni yo'q.
  • /b — xabar bilan: message — sizning matn, error — status nomi. Ya'ni javob shakli argumentga qarab o'zgaradi — mijoz ikkala shaklni ham kutishi kerak.
  • /c — NestJS 12 yangiligi: errorCode sozlamasi javobga "errorCode" maydonini qo'shadi. Mashina o'qiydigan xato kodi — 25-qismdagi Problem Details kod maydonining Nest varianti.
  • /d — massiv: ValidationPipe xatolari shunday ko'rinadi (ValidationPipe sozlamalari).
  • /e — obyekt berilsa, u to'liq tana bo'ladi.
  • /f — HttpException bo'lmagan har qanday xato — 500, xabarsiz. Nest tafsilotni mijozga bermaydi — to'g'ri xulq.

2.2 Problem Details bilan farq

Nest formati — { message, error, statusCode }. Bu standart emas, Nest'ning o'z kelishuvi. vazifalar-api esa 25-qismdan beri RFC 9457 Problem Details qaytaradi: type, title, status, detail, instance va application/problem+json. Frontend shu formatni kutadi. Shuning uchun bizga o'z filtrimiz kerak.

Tekshirib ko'ring: Kutubxona throw new UnauthorizedException() qildi. Sukut Nest filtri bilan mijoz qanday tana oladi?

Javob

{"message":"Unauthorized","statusCode":401} — /a holatidagidek: xabarsiz xatoda message — status nomi va error maydoni yo'q. Mijoz kodi error maydoniga tayansa — undefined oladi. Bitta API'da bitta xato shakli bo'lishi uchun ham o'z filtri kerak.

3. O'z filtringiz

3.1 @Catch va ExceptionFilter

Demo: domen xatosi — TopilmadiXatosi (HttpException emas, oddiy Error) — uchun filtr:

ts
class TopilmadiXatosi extends Error {
  constructor(readonly id: number) {
    super(`Vazifa ${String(id)} topilmadi`);
  }
}

@Catch(TopilmadiXatosi)
class TopilmadiFiltri implements ExceptionFilter {
  constructor(private readonly adapter: HttpAdapterHost) {}

  catch(x: TopilmadiXatosi, host: ArgumentsHost) {
    const { httpAdapter } = this.adapter;
    const ctx = host.switchToHttp();
    httpAdapter.reply(ctx.getResponse(), {
      type: 'about:blank', title: 'Not Found',
      status: 404, detail: x.message,
    }, 404);
  }
}

@Get('g') @UseFilters(TopilmadiFiltri) g() {
  throw new TopilmadiXatosi(7);
}
@Get('h') h() { throw new TopilmadiXatosi(8); }
  • @Catch(TopilmadiXatosi) — filtr qaysi xato klasslarini ushlaydi (bir nechtasi — vergul bilan). @Catch() argumentsiz — hammasini.
  • catch(xato, host) — ArgumentsHost — guard'dagi ExecutionContext ning "kichik akasi": switchToHttp() bilan getRequest()/getResponse().
  • HttpAdapterHost — Express yoki Fastify'dan qat'iy nazar javob yuborish (httpAdapter.reply). Filtr platformaga bog'lanmaydi (Fastify adapter bilan NestJS).
  • @UseFilters(...) — filtrni metod yoki controller'ga ulaydi.
text
GET /g → 404 {"type":"about:blank","title":"Not Found","status":404,"detail":"Vazifa 7 topilmadi"}
  [global filtr] TopilmadiXatosi
GET /h → 500 {"statusCode":500,"message":"Internal server error"}

/g — bizning filtr ushladi, Problem Details shaklida. /h — xuddi shu xato, lekin filtr ulanmagan: global filtrga yetdi va u uni tanimadi — 500. Domen xatosini HTTP'ga aylantirish faqat filtr ulangan joyda ishlaydi — shuning uchun bunday filtr odatda global bo'ladi.

3.2 BaseExceptionFilter dan meros

Demo'dagi global filtr Nest'ning sukut xulqini saqlab, faqat log qo'shgan:

ts
@Catch()
class HammasiFiltri extends BaseExceptionFilter {
  override catch(x: unknown, host: ArgumentsHost) {
    console.log(`  [global filtr] ` +
      `${x instanceof Error ? x.constructor.name : typeof x}`);
    super.catch(x, host);
  }
}

app.useGlobalFilters(
  new HammasiFiltri(app.get(HttpAdapterHost).httpAdapter),
);

super.catch(...) — Nest'ning o'z filtri: HttpException → uning tanasi, qolgan → 500. "Faqat log qo'shmoqchiman" degan holat uchun qulay. BaseExceptionFilter ga httpAdapter kerak — useGlobalFilters da uni qo'lda beramiz (DI yo'q).

3.3 Qaysi filtr ushlaydi

Bir nechta filtr ulangan bo'lsa, eng yaqini — xato bitta filtrga tushadi, zanjir yo'q:

flowchart TB
  X["xato tashlandi"] --> M{"metod filtri<br/>@Catch mos?"}
  M -- "ha" --> J["javob"]
  M -- "yo'q" --> C{"controller filtri<br/>mos?"}
  C -- "ha" --> J
  C -- "yo'q" --> G{"global filtr<br/>mos?"}
  G -- "ha" --> J
  G -- "yo'q" --> N["Nest sukut filtri<br/>(500 yoki HttpException)"]

Kanon lifecycle tajribasida bu ko'rinadi (kurs tajribasi, NestJS 12.2.0, Vitest ichida; har filtr 418 va o'z nomini qaytaradi):

text
2) pipe xatosi GET /t/xato → 418 {"filtr":"route"}
  …
  20. filter: route ← pipe rad etdi
4) guard rad etdi POST /t → 418 {"filtr":"controller"}
  …
   7. filter: controller ← Forbidden resource

Pipe xatosini metod filtri ushladi, global va controller filtrlari ishlamadi. To'rtinchi holatda POST /t marshrutida metod filtri yo'q edi — xatoni controller filtri oldi. Muhim nuqta: xato qaysi qatlamda tug'ilgani (pipe, guard, handler) muhim emas — filtr marshrutga qarab tanlanadi.

Filtr ichida ham xato tashlansa — uni Nest'ning sukut filtri oladi (500). Filtr kodi oddiy va xatosiz bo'lishi kerak.

4. ORM xatolari qaysi qatlamda aylansin

4.1 Ikki yo'l

Bazada noyoblik buzilsa, Prisma PrismaClientKnownRequestError (P2002) tashlaydi, pg — DatabaseError (23505). Mijozga bu 409 Takror vazifa bo'lishi kerak. Qayerda aylantiramiz?

Yo'l Qanday Kamchilik
Filtrda @Catch(Prisma.PrismaClientKnownRequestError) — P2002 → 409 filtr ORM'ni biladi; boshqa ORM — yana filtr
Ombor (repository) qatlamida ombor ORM xatosini KonfliktXatosi ga o'raydi har omborda alohida

Nest hujjatlarida va blog'larda birinchi yo'l ko'p uchraydi: "Prisma exception filter". U tez yoziladi.

4.2 Kanon tanlovi — ombor qatlami

vazifalar-api da uchta ombor bor: xotira (XotiraOmbori), pg (PgOmbori) va Prisma (PrismaOmbori) — 26–27-qismlardan. Ularning hammasi bitta VazifaOmbori interfeysini bajaradi (Repository naqshi va qatlamlar). Takror matn uchun hammasi bir xil xato tashlaydi — KonfliktXatosi (kod: "takror-matn"). Prisma omboridan parcha (kanon kodi, bo'lingan):

ts
/** Postgres SQLSTATE: adapter xatoni PrismaClientKnownRequestError
 * ichiga o'raydi — asl kodni meta'dan olamiz */
function sqlHolat(x: unknown): string | undefined {
  if (!(x instanceof Prisma.PrismaClientKnownRequestError)) {
    return undefined;
  }
  const sabab = (x.meta?.["driverAdapterError"] as
    { cause?: { originalCode?: string } } | undefined)?.cause;
  return sabab?.originalCode;
}

function takrorXatosi(x: unknown): unknown {
  const kod = sqlHolat(x);
  // 23P01 — EXCLUDE (matn noyobligi), 23505 — eski UNIQUE indeks
  if (kod === "23P01" || kod === "23505") {
    return new KonfliktXatosi("Bu vazifa ro'yxatda allaqachon bor", {
      kod: "takror-matn", sarlavha: "Takror vazifa", cause: x,
    });
  }
  return x;
}

Natija: filtr faqat IlovaXatosi ni biladi — ORM, Postgres kodlari, P2002 — hech biri. Saqlash rejimi almashsa (SAQLASH=xotira|postgres|prisma), HTTP javobi o'zgarmaydi. cause: x — asl xato yo'qolmaydi, logda ko'rinadi (Xato turlari, error.cause).

Qoida: domen xatosi — ombor/xizmatda, HTTP shakli — filtrda. Filtr "Postgres nima ekanini" bilmasa — yaxshi.

5. Hujumchi nigohi

  1. Stack va ichki xabar oqishi. res.status(500).json({ error: err.message, stack: err.stack }) — fayl yo'llari, SQL so'rovi, kutubxona versiyalari mijozga ketadi. Himoya — 500 da faqat umumiy detail va so'rov ID'si; tafsilot — logga (kanon filtri shunday).
  2. Baza xato matni. duplicate key value violates unique constraint "foydalanuvchilar_email_key" — jadval va ustun nomlari, "bu email ro'yxatda bor" degan ma'lumot. Himoya — ombor qatlamida domen xatosiga aylantirish; email uchun javob matnini "ro'yxatdan o'tish xatosi" kabi umumiy qilish (28-qismdagi hisob sanash himoyasi).
  3. Filtrdagi xato. Filtr xato.response.message ni tekshirmay o'qisa va xato — null yoki satr bo'lsa — filtrning o'zi yiqiladi va javob — Nest'ning sukut 500 i, logsiz. Himoya — filtrda instanceof tekshiruvlari va unknown turi (kanon filtri catch(xato: unknown, ...)).
  4. Yarim yuborilgan javob. Oqim (stream) yoki CSV eksport yarmida xato bo'lsa — sarlavhalar allaqachon ketgan; res.status(500) ikkinchi xatoni beradi. Himoya — res.headersSent tekshiruvi: faqat log va ulanishni yopish.

6. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Domen xatosi 500 bo'ldi filtr ulanmagan yoki @Catch mos emas global filtr yoki @Catch(Klass)
Ba'zi javoblarda error maydoni yo'q new NotFoundException() xabarsiz o'z filtri bilan bir xil shakl
Filtrda logger undefined useGlobalFilters(new F()) — DI yo'q APP_FILTER
Cannot set headers after they are sent javob yarmida xato res.headersSent tekshiruvi
Filtr ishlamaydi, sukut javob filtr ichida xato filtrni unknown bilan, tekshiruvlar bilan
Ikki filtr ham ishladi deb kutildi faqat eng yaqin filtr bitta filtrda hamma holat

7. Mashqlar

Mashqlar kurs/mashqlar/30/24-filtrlar/ da.

1-mashq (oson): to'ldiring

  • Hamma xatoni ushlaydigan filtr — @()
  • Filtr metodining ikkinchi argumenti turi —
  • 409 uchun tayyor xato — new ("Takror")
  • NestJS 12 da mashina o'qiydigan kod — { : "TAKROR_MATN" }
  • DI bilan global filtr tokeni —
Yechim
  • Catch — argumentsiz — hamma xato; @Catch(A, B) — faqat shular.
  • ArgumentsHost — switchToHttp().getRequest()/getResponse().
  • ConflictException — HttpException oilasidan.
  • errorCode — javobda "errorCode" maydoni.
  • APP_FILTER — useGlobalFilters(new …) dan farqli, DI bilan.

2-mashq (o'rta): filtr tanlash modeli

Filtrlar ro'yxati — { nom, ushlaydi: [Klass, ...] }; bo'sh ushlaydi — hamma xato. filtrTop(qatlamlar, xato) funksiyasini yozing: qatlamlar — [metodFiltrlari, controllerFiltrlari, globalFiltrlar] (har biri massiv). Birinchi mos kelgan filtr nomini qaytarsin, hech biri mos kelmasa — "nest-sukut". Moslik — instanceof (meros ham hisoblanadi). Ishora: find, some (Massivda qidirish va tekshirish).

Yechim
js
class IlovaXatosi extends Error {}
class TopilmadiXatosi extends IlovaXatosi {}

function filtrTop(qatlamlar, xato) {
  for (const qatlam of qatlamlar) {
    const f = qatlam.find((f) => f.ushlaydi.length === 0
      || f.ushlaydi.some((K) => xato instanceof K));
    if (f) return f.nom;
  }
  return "nest-sukut";
}

const metod = [{ nom: "topilmadi", ushlaydi: [TopilmadiXatosi] }];
const global = [{ nom: "problem", ushlaydi: [IlovaXatosi] }];

console.log(filtrTop([metod, [], global], new TopilmadiXatosi()));
console.log(filtrTop([[], [], global], new TopilmadiXatosi()));
console.log(filtrTop([metod, [], global], new TypeError("x")));
const hammasi = [{ nom: "hammasi", ushlaydi: [] }];
console.log(filtrTop([[], [], hammasi], new TypeError("x")));

Konsolda:

text
topilmadi
problem
nest-sukut
hammasi

Ikkinchi qator — meros: TopilmadiXatosi ham IlovaXatosi, global filtr uni ushladi. Uchinchisi — TypeError hech qaysi @Catch ga mos emas: Nest'ning sukut filtri (500). Shuning uchun kanon filtri @Catch() — argumentsiz: kutilmagan xatolar ham Problem Details shaklida chiqsin.

3-mashq (qiyin): Problem Details yasovchi

problem(xato, so'rov) funksiyasini yozing — kanon filtrining mantiqi, toza funksiya sifatida. Kirish: xato — { holat, xabar, kod } (bizning xato), { getStatus, message } (Nest HttpException modeli) yoki boshqa narsa. Chiqish: { status, tana }, tana — type, title, status, detail, instance. Qoidalar: bizning xato — o'z statusi va kod dan type (https://vazifalar-api.example/xatolar/<kod>); HttpException < 500 — status saqlanadi, type: "about:blank"; qolgan — 500 va detail: "Kutilmagan xato", asl xabar chiqmasin. title uchun kichik jadval (404, 409, 500) yetadi.

Yechim
js
const NOMLAR = {
  404: "Not Found", 409: "Conflict", 500: "Internal Server Error",
};

function problem(xato, sorov) {
  const instance = sorov.originalUrl;
  if (xato?.holat !== undefined) {
    const type = xato.kod
      ? `https://vazifalar-api.example/xatolar/${xato.kod}`
      : "about:blank";
    return {
      status: xato.holat,
      tana: { type, title: NOMLAR[xato.holat], status: xato.holat,
        detail: xato.xabar, instance },
    };
  }
  const nest = typeof xato?.getStatus === "function";
  if (nest && xato.getStatus() < 500) {
    const s = xato.getStatus();
    return {
      status: s,
      tana: { type: "about:blank", title: NOMLAR[s], status: s,
        detail: xato.message, instance },
    };
  }
  return {
    status: 500,
    tana: { type: "about:blank", title: NOMLAR[500], status: 500,
      detail: "Kutilmagan xato", instance },
  };
}

const s = { originalUrl: "/v1/vazifalar/7" };
const holatlar = [
  { holat: 409, xabar: "Takror", kod: "takror-matn" },
  { getStatus: () => 404, message: "Cannot GET /yoq" },
  new TypeError("ombor.top is not a function"),
];
for (const x of holatlar) console.log(JSON.stringify(problem(x, s)));

Konsolda:

text
{"status":409,"tana":{"type":"https://vazifalar-api.example/xatolar/takror-matn","title":"Conflict","status":409,"detail":"Takror","instance":"/v1/vazifalar/7"}}
{"status":404,"tana":{"type":"about:blank","title":"Not Found","status":404,"detail":"Cannot GET /yoq","instance":"/v1/vazifalar/7"}}
{"status":500,"tana":{"type":"about:blank","title":"Internal Server Error","status":500,"detail":"Kutilmagan xato","instance":"/v1/vazifalar/7"}}

Uchinchi qatorda ombor.top is not a function — yo'q. Kutilmagan xato matni ichki tuzilmani ochadi; u faqat logga boradi. Kanon filtri qo'shimcha ravishda Nest router'ining 404 xabarini ("Cannot GET /yoq") o'zimizning "bunday manzil yo'q" xabariga almashtiradi.

8. Loyiha qadami: Problem Details filtri — APP_FILTER

8.1 Nima o'zgaradi

Hozir filtr ilova.ts da yaratiladi: app.useGlobalFilters(new ProblemDetailsFilter((_id, xato) => { joriyLog(log).error(...) })) — log funksiyasi constructor argumenti sifatida qo'lda beriladi. Endi filtr DI'dan LOG tokeni orqali logger oladi va AppModule da APP_FILTER bilan ulanadi.

Qo'shimcha foyda: testlar AppModule.forRoot(...) dan ilova yasasa, filtr ham o'z-o'zidan ulanadi — useGlobalFilters ni testda takrorlash shart emas.

8.2 src/umumiy/problem.filter.ts

Kanon faylida qatorlar uzun edi — bo'ldik va izohlarni qisqartirdik (kod ma'nosi aynan); nusxani kanon nusxasida tsc --noEmit, ESLint va Vitest bilan tekshirdik.

ts
// problem.filter.ts — global exception filter: HAMMA xato bir
// joyda Problem Details'ga aylanadi (25-qism xato-ishlovchi.ts
// ning Nest shakli). @Catch() — argumentsiz: har qanday xato
// (IlovaXatosi, Nest HttpException, kutilmagan TypeError…).
// Global — APP_FILTER (DI bilan: LOG tokeni)
import {
  Catch, HttpException, Inject, NotFoundException,
} from "@nestjs/common";
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
import type { Request, Response } from "express";
import { IlovaXatosi } from "./xatolar.ts";
import { problemYasa, problemYubor } from "./problem.ts";
import { joriyLog } from "./log.ts";
import type { Log } from "./log.ts";
import { LOG } from "../tokenlar.ts";

@Catch()
export class ProblemDetailsFilter implements ExceptionFilter {
  constructor(@Inject(LOG) private readonly log: Log) {}

  /** to'liq xato (stack bilan) — faqat logga; sorovId joriy so'rov
   * logger'idan (AsyncLocalStorage) */
  private logla(xato: unknown): void {
    joriyLog(this.log).error({ err: xato }, "kutilmagan xato");
  }

  catch(xato: unknown, host: ArgumentsHost): void {
    const http = host.switchToHttp();
    const req = http.getRequest<Request>();
    const res = http.getResponse<Response>();
    if (res.headersSent) {
      // javob yarmida — status o'zgartirib bo'lmaydi: log va
      // ulanishni yopamiz
      this.logla(xato);
      res.end();
      return;
    }
    const umumiy = {
      instance: req.originalUrl, sorovId: req.sorovId,
    };
    if (xato instanceof IlovaXatosi) {
      res.set(xato.httpSarlavhalar);
      problemYubor(res, problemYasa(xato.holat, {
        ...umumiy, kod: xato.kod, sarlavha: xato.sarlavha,
        detail: xato.message, xatolar: xato.xatolar,
      }));
      return;
    }
    // Nest router'i mos marshrut topmasa NotFoundException ("Cannot
    // GET /yoq") tashlaydi — o'zimizning xabar (Express'dagidek)
    if (xato instanceof NotFoundException) {
      problemYubor(res, problemYasa(404, {
        ...umumiy,
        detail: `${req.method} ${req.path} — bunday manzil yo'q`,
      }));
      return;
    }
    // Boshqa Nest HttpException'lari (kutubxonalar tashlaydi) —
    // status saqlanadi, 5xx tafsiloti yashiriladi
    if (xato instanceof HttpException && xato.getStatus() < 500) {
      problemYubor(res, problemYasa(xato.getStatus(), {
        ...umumiy, detail: xato.message,
      }));
      return;
    }
    // Kutilmagan (dasturchi xatosi, DB uzilishi…): tafsilot
    // mijozga BERILMAYDI — faqat logga
    this.logla(xato);
    problemYubor(res, problemYasa(500, {
      ...umumiy,
      detail: "Kutilmagan xato. sorovId bilan murojaat qiling.",
    }));
  }
}

Avvalgi holatdan farq:

  • Constructor — @Inject(LOG) private readonly log: Log. Avval XatoLogi turidagi funksiya edi, sukut qiymati console.error bilan. Endi LOG — global BogliqliklarModule dagi pino logger (Custom provider'lar).
  • logla(xato) — joriyLog(this.log) so'rov kontekstidagi bola logger'ni oladi (unda sorovId bor — 25-qismdagi AsyncLocalStorage). Shuning uchun sorovId ni argument qilib uzatish shart emas.
  • catch ning mantiqi — 3-mashqdagi tartib: bizning xato → Nest 404 → boshqa HttpException < 500 → qolgan hammasi 500.

8.3 Ulash

app.module.ts da providers ga uchinchi global enhancer qo'shiladi (importga APP_FILTER va ProblemDetailsFilter):

ts
// global enhancer'lar DI bilan: Standard Schema tekshiruvi, 415
// guard'i, Problem Details filtri
providers: [
  { provide: APP_PIPE, useClass: SxemaPipe },
  { provide: APP_GUARD, useClass: JsonTanaGuard },
  { provide: APP_FILTER, useClass: ProblemDetailsFilter },
],

ilova.ts dan useGlobalFilters qatori va endi kerak bo'lmagan importlar o'chadi:

diff
-import { ProblemDetailsFilter } from "./umumiy/problem.filter.ts";
-import { joriyLog, logYarat, sorovLogi } from "./umumiy/log.ts";
+import { logYarat, sorovLogi } from "./umumiy/log.ts";
 …
-  app.useGlobalFilters(new ProblemDetailsFilter((_id, xato) => {
-    joriyLog(log).error({ err: xato }, "kutilmagan xato");
-  }));
+  // Problem Details filtri — AppModule'da APP_FILTER (DI bilan)

test/marshrutlar.test.ts — ilovani AppModule.forRoot dan yasaydi va filtrni o'zi ulardi. Endi filtr modul ichida, va eski chaqiruv tur xatosi ham beradi (constructor argumenti Log bo'ldi):

diff
-import { ProblemDetailsFilter }
-  from "../src/umumiy/problem.filter.ts";
 …
   app = await NestFactory.create(SinovModuli, { logger: false });
-  app.useGlobalFilters(new ProblemDetailsFilter(() => undefined));
   await app.init();

8.4 Tekshiruv

npm run tip, npm run lint, npm test — toza (Postgres'siz: Tests 75 passed | 6 skipped (81)). marshrutlar.test dagi 405 tekshiruvlari Problem Details javobini kutadi — u o'tgani filtr testda ham ulanganini ko'rsatadi. Hali commit qilmang.

9. Real ishda

  • Har jiddiy Nest loyihasida bitta global filtr bor: xato formati (ko'pincha RFC 9457 yoki kompaniya standarti), log, Sentry'ga yuborish.
  • "Prisma exception filter" — mashhur naqsh; review'da savol: "boshqa ORM yoki keshdan kelgan xato ham shu filtrdan o'tadimi?".
  • Intervyu: "HttpException va oddiy Error farqi Nest'da?", "filtrlar qaysi tartibda tanlanadi?", "500 javobida nima bo'lishi kerak?".

Xulosa

  • Filtr — xatoni javobga aylantiradi: @Catch(Klass) + ExceptionFilter.catch(xato, host); @Catch() — hammasi.
  • Nest sukut filtri: HttpException → { message, error?, statusCode } (shakli argumentga bog'liq), boshqasi → 500; NestJS 12 — errorCode.
  • Eng yaqin filtr ushlaydi: metod → controller → global → Nest sukut; xato bitta filtrga tushadi.
  • BaseExceptionFilter — sukut xulqni saqlab kengaytirish; HttpAdapterHost — platformadan mustaqil javob.
  • ORM xatolari — ombor qatlamida domen xatosiga; filtr faqat HTTP shaklini biladi.
  • vazifalar-api: ProblemDetailsFilter — APP_FILTER, LOG DI'dan.

Keyingi dars: Custom dekoratorlar — createParamDecorator bilan @IfMatchShart() va @ToplamYoli(), applyDecorators bilan @VazifaYozuvi() — controller'lardan @Req() ni deyarli butunlay olib tashlaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Exception filter'lar NestJS'da: HttpException, @Catch, ArgumentsHost va Problem Details — IlmHamroh