Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki shakl
- 2.1 Funksional middleware
- 2.2 Klass middleware
- 3. Ulash: configure va MiddlewareConsumer
- 3.1 NestModule
- 3.2 Demo: kim qachon ishladi
- 4. Ikki tuzoq
- 4.1 Ikki marta ishlash
- 4.2 req.path kesiladi
- 5. app.use() yoki modul middleware'i
- 5.1 Middleware nimani bilmaydi
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): "bir so'rovga bir marta"
- 3-mashq (qiyin): eskirgan modeli
- 9. Loyiha qadami: idempotentlik va eskirgan — Nest middleware'lari
- 9.1 Nima o'zgaradi
- 9.2 Token va provider
- 9.3 src/umumiy/idempotentlik.middleware.ts
- 9.4 Ulash
- 9.5 eskirgan.ts va ilova.ts
- 9.6 Tekshiruv — va tuzatishlarsiz nima bo'lardi
- 10. Real ishda
- Xulosa
- Manbalar
Middleware Nest'da: NestMiddleware, MiddlewareConsumer, forRoutes va ikki marta ishlash tuzog'i
Qisqacha: Nest'da middleware — o'sha Express middleware'i (
(req, res, next)), lekin ikki shaklda: funksional (oddiy funksiya) va klass (NestMiddlewareninguse()metodi, DI bilan bog'liqlik oladi). Ular modulda ulanadi: modulNestModuleni bajaradi vaconfigure(consumer)ichidaconsumer.apply(X).forRoutes(...)yozadi;exclude(...)— istisno yo'llar. Har marshrutga kerak bo'lgan, 404 ga ham ishlashi kerak bo'lganlar —app.use()da qoladi. Kanon ikki haqiqiy tuzoq topgan:forRoutes(Controller)bilan middleware bitta so'rovda ikki marta ishlashi mumkin, yo'l bilan ulanganda esareq.pathkesiladi.
Bu darsda
- Funksional va klass middleware'ni yozasiz va klass middleware'ga DI bilan bog'liqlik berasiz.
NestModule.configure,MiddlewareConsumer,forRoutesvaexcludebilan middleware'ni kerakli marshrutlarga ulaysiz.app.use()va modul middleware'i farqini, qaysi birini qachon tanlashni bilasiz.- Nest 12.2 ning ikki kamchiligini haqiqiy test xatolarida ko'rasiz va tuzatasiz.
vazifalar-apining idempotentlik va eskirgan yo'l middleware'larini Nest modullariga ko'chirasiz.
Oldin bilishingiz kerak: Middleware va request lifecycle, Idempotency-Key, API versiyalash va deprecation, Request lifecycle, Custom provider'lar.
Validatsiya darslari tugadi — kirish tekshiruvi pipe'da, 415 — guard'da. Endi so'rov hayotining eng boshiga qaytamiz: middleware.
1. Nega bu kerak?
25-qismda vazifalar-api ning hamma "o'rta qatlami" middleware edi: sorovId, helmet, cors, express.json, idempotentlik, eskirgan yo'llar. Nest'ga ko'chganimizda ularni Bootstrap darsida app.use() ga o'zgarishsiz qo'ydik — ishlaydi.
Lekin ikkitasi noqulay qoldi:
- Idempotentlik omborga muhtoj (
IdempotentlikOmbori— testda soxta soatli, 27-qismda Redis).app.use(idempotentlik(ombor))— omborilova.tsda qo'lda yaratiladi va uzatiladi. Nest'da esa bog'liqliklar DI konteyneridan keladi. - Qaysi yo'llarga —
app.use(["/v1/vazifalar", "/vazifalar"], ...): yo'llar qo'lda, satr bilan. Controller'ning yo'li o'zgarsa — bu satr eskiradi.
Nest'da middleware'ni modul ulaydi va uni controller'ga bog'lash mumkin. DI ham ishlaydi. Bu dars — shu usul va uning ikki kutilmagan tomoni.
O'xshatish: bino kirish eshigidagi qorovul va qavatdagi kotiba. Qorovul (app.use) hamma kiruvchini ko'radi — hatto adashib kirganlarni ham. Kotiba (modul middleware) faqat o'z bo'limiga kelganlarni qabul qiladi va bo'lim jurnalidan (DI) foydalanadi.
| Express (25-qism) | Nest modul middleware'i | |
|---|---|---|
| Ulash | app.use(yollar, mw) yoki router.use(mw) |
consumer.apply(mw).forRoutes(...) |
| Bog'liqlik | yopilish orqali qo'lda (idempotentlik(ombor)) |
constructor orqali DI |
| Yo'l | satr | controller klassi, satr yoki { path, method } |
| Kutilmagan farq | — | bitta so'rovda ikki marta ishlashi mumkin (pastda) |
2. Ikki shakl
2.1 Funksional middleware
Bu — 25-qismdan tanish Express middleware'i (Middleware va request lifecycle). Nest'da hech narsa o'zgarmaydi:
function funksional(req: Request, res: Response, next: NextFunction) {
res.set('X-Funksional', 'ha');
next();
}Bog'liqlik kerak bo'lsa — yopilish (Closure) orqali: eskirgan(ESKI_YOLLAR) — funksiya middleware qaytaradi. Kanonning eskirgan i aynan shunday.
2.2 Klass middleware
@Injectable()
class KlassMiddleware implements NestMiddleware {
constructor(private readonly soat: Soat) {}
use(req: Request, _res: Response, next: NextFunction) {
console.log(`klass ${req.method} ${req.originalUrl} ` +
`(${this.soat.hozir()})`);
next();
}
}NestMiddleware— bittause(req, res, next)metodi bor interfeys.@Injectable()— Nest uni provider kabi yaratadi va constructor'iga bog'liqliklarni beradi.Soat— modulningprovidersidagi xizmat.
Klass middleware'ning yagona afzalligi — DI. Bog'liqlik kerak bo'lmasa, funksional yozing — u soddaroq. Nest hujjati ham shuni tavsiya qiladi.
Klass middleware'ning yana bir xususiyati: u provider kabi bir marta yaratiladi va hamma so'rovlarda bitta nusxa ishlaydi. Shuning uchun klass maydonida so'rovga oid narsa (masalan, this.joriyKalit) saqlamang — parallel so'rovlar bir-birining qiymatini bosib ketadi. So'rovga oid ma'lumot req da yoki mahalliy o'zgaruvchida turadi.
3. Ulash: configure va MiddlewareConsumer
3.1 NestModule
Middleware @Module({...}) ichida emas, modul klassining configure() metodida ulanadi:
@Module({ controllers: [VazifalarController], providers: [Soat] })
class M implements NestModule {
configure(c: MiddlewareConsumer) {
c.apply(KlassMiddleware)
.exclude({
path: 'vazifalar/sogliq', method: RequestMethod.GET,
})
.forRoutes(VazifalarController);
c.apply(yolKor).forRoutes('*splat');
c.apply(funksional).forRoutes({
path: 'vazifalar',
method: RequestMethod.POST,
});
}
}apply(...)— bir yoki bir nechta middleware (vergul bilan, tartibda bajariladi).forRoutes(...)— qayerga: controller klassi, yo'l satri ('*splat'— Express 5 yozuvi, "hamma yo'l"; Express 5 route sintaksisi) yoki{ path, method }.exclude(...)— istisnolar,forRoutesdan oldin yoziladi.
3.2 Demo: kim qachon ishladi
Demo loyihada VazifalarController da to'rtta marshrut: @Get(), @Post(), @All() (405 uchun, Controller'lar darsidagidek) va @Get('sogliq'). Har middleware har chaqirilganida hisoblagichga yozdi. Chiqishda har so'rov uchun ikki narsaga qarang: qaysi middleware'lar ishladi va necha marta (2 × belgisi) (biz ishga tushirdik: NestJS 12.2.0, Express 5.3.0, Node 24.21.0):
GET /vazifalar → 200 x-funksional=null
2 × klass GET /vazifalar (soat-xizmati)
1 × yolKor path=/ originalUrl=/vazifalar
POST /vazifalar → 201 x-funksional=ha
2 × klass POST /vazifalar (soat-xizmati)
1 × yolKor path=/ originalUrl=/vazifalar
1 × funksional POST /vazifalar path=/vazifalar
GET /vazifalar/sogliq → 200 x-funksional=null
1 × yolKor path=/ originalUrl=/vazifalar/sogliq
GET /yoq → 404 x-funksional=null
1 × yolKor path=/ originalUrl=/yoqHar qatorni o'qing:
- DI ishladi —
(soat-xizmati): klass middlewareSoatxizmatini oldi. { path, method }—funksionalfaqatPOST /vazifalarda (x-funksional=ha),GETda — yo'q.exclude—GET /vazifalar/sogliqda klass middleware ishlamadi./yoq— mavjud bo'lmagan yo'l:forRoutes(VazifalarController)dagi middleware ishlamadi.'*splat'dagisi esa ishladi.
Va ikkita g'alati narsa: klass middleware har so'rovda ikki marta (2 ×) ishladi, yolKor esa path=/ ni ko'rdi, garchi so'rov /vazifalar ga bo'lsa ham. Ikkalasi — keyingi bo'limda.
4. Ikki tuzoq
4.1 Ikki marta ishlash
forRoutes(VazifalarController) Nest'ga "shu controller'ning marshrutlari" deydi. Nest controller'ning har marshruti uchun middleware'ni alohida ro'yxatdan o'tkazadi — yo'li va metodi bilan. @Get(), @Post() va @All() ning yo'li bir xil (/vazifalar). GET /vazifalar so'roviga ikkita yozuv mos keladi: GET niki va ALL niki (@All — hamma metod). Har biri middleware'ni chaqirdi.
Buni brauzerda kichik modelda ko'raylik — har yozuv yo'l va metod bo'yicha moslanadi:
function ilova() {
const qatlamlar = [];
return {
use(metod, yol, mw) { qatlamlar.push({ metod, yol, mw }); },
sorov(metod, url) {
const izlar = [];
for (const q of qatlamlar) {
const metodMos = q.metod === "ALL" || q.metod === metod;
if (metodMos && url === q.yol) q.mw(`${metod} ${url}`, izlar);
}
return izlar;
},
};
}
const marshrutlar = [
{ metod: "GET", yol: "/vazifalar" },
{ metod: "POST", yol: "/vazifalar" },
{ metod: "ALL", yol: "/vazifalar" },
{ metod: "GET", yol: "/vazifalar/sogliq" },
];
const mw = (url, izlar) => izlar.push(`mw ${url}`);
// forRoutes(Controller): har marshrut uchun alohida yozuv
const a = ilova();
for (const m of marshrutlar) a.use(m.metod, m.yol, mw);
console.log(a.sorov("GET", "/vazifalar"));
console.log(a.sorov("POST", "/vazifalar"));
console.log(a.sorov("GET", "/vazifalar/sogliq"));Konsolda:
[ 'mw GET /vazifalar', 'mw GET /vazifalar' ]
[ 'mw POST /vazifalar', 'mw POST /vazifalar' ]
[ 'mw GET /vazifalar/sogliq' ]Model demo bilan bir xil natija berdi: GET va POST — ikki martadan (o'z yozuvi + ALL yozuvi), /sogliq — bir marta (u yo'lda @All yo'q). Model soddalashtirilgan: haqiqiy Express prefiks bo'yicha ham moslaydi va Nest bitta forRoutes ichidagi aynan bir xil yozuvlarni o'zi birlashtiradi. Lekin bir yo'ldagi turli metodli marshrutlar — alohida yozuvlar, va @All ularning har biriga "qo'shiladi".
Ko'p middleware uchun ikki marta ishlash zararsiz (log ikki qator, sarlavha qayta qo'yiladi). Lekin holatli middleware uchun — halokatli. Idempotentlik middleware'i birinchi chaqiruvda kalitni "jarayonda" deb yozadi. Ikkinchi chaqiruv o'sha kalitni ko'radi va o'ylaydi: "bu so'rov hali bajarilmoqda" — 409.
sequenceDiagram
participant M as Mijoz
participant A as idempotentlik (1)
participant B as idempotentlik (2)
participant O as Ombor
M->>A: POST /v1/vazifalar, kalit K
A->>O: boshla(K)
O-->>A: "yangi" (K = jarayonda)
A->>B: next()
B->>O: boshla(K)
O-->>B: "jarayonda"
B-->>M: 409 So'rov bajarilmoqdaMijoz birinchi urinishdayoq 409 oldi — vazifa yaratilmadi. Bu xatoni kanon testlari ushlagan. Biz uni qayta ko'rsatdik: «Loyiha qadami» dagi tuzatishni o'chirib, testlarni yurgizdik («Loyiha qadami» da chiqish).
Tuzatish — middleware o'zi "bu so'rovni ko'rdimmi?" ni eslab qoladi. So'rov obyekti — kalit, WeakSet — xotira: so'rov tugagach, yozuv o'z-o'zidan yo'qoladi.
4.2 req.path kesiladi
yolKor forRoutes('*splat') bilan ulangan va path=/ ni ko'rdi. Sabab — Express'ning app.use(yol, mw) qoidasi: middleware ichida req.path — ulangan yo'ldan keyingi qism (req obyekti). '*splat' butun yo'lni "yeb" qo'ydi va req.path ga / qoldi. req.originalUrl esa doim to'liq: /vazifalar/sogliq.
25-qismdagi eskirgan() aynan req.path dan foydalanardi — app.use(eskirgan(...)) (yo'lsiz) bilan bu to'g'ri edi. Nest middleware'iga ko'chganda u jim buzildi: Deprecation sarlavhalari chiqmay qoldi. Xato yo'q, 200 javob, faqat sarlavhalar yo'q — buni ham test ushladi.
Qoida: yo'l bilan ulangan middleware'da yo'lni req.originalUrl dan oling.
Tekshirib ko'ring:
consumer.apply(logla).forRoutes("vazifalar")va middleware ichidaconsole.log(req.path).GET /vazifalar/7so'rovi nima chiqaradi?
Javob
/7. Middleware vazifalar yo'liga ulangan — Express shu qismni kesadi va req.path da qolgani qoladi. To'liq yo'l req.originalUrl da: /vazifalar/7 (query bo'lsa, u ham shu yerda — split("?")[0] bilan ajratiladi).
5. app.use() yoki modul middleware'i
app.use() (ilova.ts) |
modul configure() |
|
|---|---|---|
| Qachon ishlaydi | Nest marshrutlaridan oldin, har so'rovda (404 ham) | faqat forRoutes dagi marshrutlarda |
| DI | yo'q (qo'lda uzatiladi) | bor (klass middleware) |
| Yo'llar | satr bilan, qo'lda | controller yoki yo'l |
| Tartib | yozilgan tartibda, modul middleware'laridan oldin | modullar tartibida |
Request lifecycle dagi kanon tajribasida tartib aynan shunday edi:
1. middleware: global (app.use)
2. middleware: modul (MiddlewareConsumer)
3. guard: global (APP_GUARD)Nima bo'ldi? Uchta qator — uchta qatlam. Avval app.use dagi middleware, keyin modul middleware'i, faqat shundan keyin guard. Ya'ni modul middleware'i guard'dan oldin ishlaydi: idempotentlik kalitini tekshirish ruxsat tekshiruvidan oldin bo'ladi. Hozircha bu muammo emas — loyihada auth yo'q. Lekin 28-qism auth'i Nest'ga ko'chganda aynan shu tartib haqiqiy xato beradi: idempotentlik guard'ning 401 javobini ham eslab qoladi. Uni Access/refresh token va cookie NestJS'da darsida topamiz va tuzatamiz.
Tanlov qoidasi:
- Har so'rovga kerak (404 ham), DI kerak emas —
app.use():sorovId, so'rov logi,helmet,cors, statik fayllar,express.json,tanaXatosi. Masalan, 404 javobida hamX-Request-Idva xavfsizlik sarlavhalari bo'lishi kerak. - Ma'lum marshrutlarga yoki DI kerak — modul middleware'i: idempotentlik (faqat vazifalar to'plami, ombor DI'dan).
eskirgan() o'rtada: u hamma yo'lga ulanadi ('*splat'), lekin AppModule.configure da — ilova sozlamalari bir joyda (modulda) bo'lsin degan qaror bilan. app.use() da qolsa ham ishlardi.
5.1 Middleware nimani bilmaydi
Klass middleware DI oladi va Nest modulida yashaydi — lekin u baribir Express middleware'i. Unga (req, res, next) beriladi, boshqa hech narsa. Ikki narsa yo'q:
- Qaysi handler ishlashi. Middleware ishlaganda Nest hali marshrutni handler'ga ulamagan. Shuning uchun
@JsonTana()yoki@Rollar("admin")kabi belgilarni middleware o'qiy olmaydi —Reflectorga beradigangetHandler()yo'q. - Javobning natijasi. Middleware
next()ni chaqiradi va handler qaytargan qiymatni ko'rmaydi. "Javobga ETag qo'sh" kabi ish — interceptor'niki (Interceptor'lar va RxJS).
Shu sababli Nest'da qoida: marshrutga bog'liq qaror (kirish mumkinmi, tana kerakmi) — guard'da, so'rovning umumiy tayyorgarligi (id, log, sarlavhalar, tana o'qish) — middleware'da. Idempotentlik o'rtada turadi: u marshrutga bog'liq, lekin handler belgisini o'qimaydi — faqat sarlavha va tanani. Shuning uchun u middleware bo'lib qoldi.
Tekshirib ko'ring:
helmet()niVazifalarModule.configureichidaforRoutes(VazifalarController)bilan ulasangiz,GET /yoq(404) javobida xavfsizlik sarlavhalari bo'ladimi?
Javob
Yo'q. forRoutes(VazifalarController) faqat shu controller marshrutlarida ishlaydi — demo chiqishidagi /yoq qatorida modul middleware'i ishlamagan edi. 404 javobi ham brauzerga boradi va himoya sarlavhalari unga ham kerak. Shuning uchun helmet — app.use() da, hamma so'rovga.
6. Hujumchi nigohi
- Middleware'ni chetlab o'tish.
forRoutes("vazifalar")faqat shu yo'lni qamraydi; eskirgan versiyasiz yo'l, katta-kichik harf (/Vazifalar) yoki oxiridagi/boshqacha moslanishi mumkin. Agar middleware xavfsizlik ishini qilsa (masalan, imzo tekshiruvi) — chetlab o'tilgan yo'l himoyasiz. Himoya — xavfsizlik tekshiruvlari guard'da (marshrutga bog'langan, DI bilan) yokiapp.use()da (hamma so'rovga); har yo'l varianti uchun test. - Ikki marta ishlashni suiiste'mol qilish. Holatli middleware (limit hisoblagichi, idempotentlik) ikki marta ishlasa — limit ikki barobar tez tugaydi yoki 409 qaytadi. Hujumchi buni o'zi qila olmaydi, lekin xato hamma foydalanuvchiga ta'sir qiladi. Himoya — "bir so'rovga bir marta" himoyasi va test.
req.pathga tayangan qaror. Yo'l bilan ulangan middleware'dareq.pathqisqa:if (req.path.startsWith("/admin"))tekshiruvi hech qachon ishlamasligi mumkin. Himoya —req.originalUrl.- Idempotentlik kalitini tahmin qilish. Kalit faqat mijozga ma'lum bo'lishi kerak; tana izi (
fingerprint) tekshiriladi — boshqa tana bilan bir xil kalit 422 (Idempotency-Key).
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Middleware ishlamaydi | modul configure da ulanmagan yoki modul import qilinmagan |
implements NestModule + configure |
Klass middleware'da bog'liqlik undefined |
@Injectable() yo'q yoki provider modulda emas |
@Injectable() + providers |
| Bir so'rovda ikki marta | forRoutes(Controller), bir yo'lda bir nechta metod |
WeakSet<Request> himoyasi |
| 404 da sarlavha yo'q | middleware forRoutes(Controller) da |
app.use() |
req.path — / |
yo'l bilan ulangan | req.originalUrl |
| Parallel so'rovlarda qiymatlar aralashadi | klass middleware maydonida so'rov holati | holat req da yoki mahalliy o'zgaruvchida |
TS2339: Property 'exclude' does not exist on type 'MiddlewareConsumer'. |
exclude forRoutes dan keyin yozilgan |
exclude(...).forRoutes(...) |
Oxirgi qatordagi xatoni biz demo loyihada tsc bilan oldik (TypeScript 6.0.3). Tarjimasi: "MiddlewareConsumer turida exclude xususiyati yo'q". forRoutes zanjirni yopadi va yana consumer ni qaytaradi — unda faqat apply bor. Ya'ni bu xato jim o'tmaydi, kompilyator darhol ushlaydi. Tartib doim: apply → exclude → forRoutes.
8. Mashqlar
Mashqlar kurs/mashqlar/30/21-middleware/ da.
1-mashq (oson): to'ldiring
- Klass middleware bajaradigan interfeys —
- Middleware ulanadigan modul metodi —
- Hamma yo'l (Express 5) —
forRoutes("") - Istisno yo'l —
.({ path: "vazifalar/sogliq", method: RequestMethod.GET }) - Yo'l bilan ulangan middleware'da to'liq yo'l —
req.
Yechim
- NestMiddleware —
use(req, res, next); DI uchun@Injectable(). - configure — modul
NestModuleni bajaradi, argument —MiddlewareConsumer. - *splat — Express 5 da nomli wildcard; eski
*ishlamaydi. - exclude —
forRoutesdan oldin. - originalUrl —
req.pathulangan qismsiz.
2-mashq (o'rta): "bir so'rovga bir marta"
birMarta(mw) funksiyasini yozing: istalgan middleware'ni o'raydi va bitta so'rov obyekti uchun uni faqat birinchi chaqiruvda ishga tushiradi; keyingilarida darhol next(). So'rov tugagach xotira bo'shashi uchun WeakSet ishlating (WeakMap, WeakSet). Sinash uchun bitta so'rov obyektini ikki marta uzating.
Yechim
function birMarta(mw) {
const korilgan = new WeakSet();
return (req, res, next) => {
if (korilgan.has(req)) {
next();
return;
}
korilgan.add(req);
mw(req, res, next);
};
}
let hisob = 0;
const sanovchi = (req, res, next) => {
hisob++;
next();
};
const himoyali = birMarta(sanovchi);
const req1 = { url: "/vazifalar" };
const req2 = { url: "/vazifalar" };
const next = () => {};
himoyali(req1, {}, next);
himoyali(req1, {}, next);
himoyali(req2, {}, next);
console.log(hisob);Konsolda:
2Uch chaqiruv, ikki hisob: req1 ikkinchi marta o'tkazib yuborildi, req2 — boshqa so'rov, u hisoblandi. Kanon IdempotentlikMiddleware aynan shu g'oyani ishlatadi. Set emas, WeakSet — chunki Set so'rov obyektlarini abadiy ushlab turardi va server xotirasi har so'rov bilan o'sardi.
3-mashq (qiyin): eskirgan modeli
25-qismdagi eskirgan(yollar) ni toza funksiya sifatida yozing: sarlavhalar(url, yollar) — url ning yo'l qismi (query'siz) eskirgan prefiks bilan boshlansa, { Deprecation, Link } qaytarsin, aks holda null. Link — voris yo'l + qolgan qism: /vazifalar/7 → </v1/vazifalar/7>; rel="successor-version". Deprecation — @1792800000. Prefiks moslashi aniq bo'lsin: /vazifalarim — mos emas. Ishora: startsWith, slice (Satr metodlari).
Yechim
const ESKI_YOLLAR = [
{ prefiks: "/vazifalar", voris: "/v1/vazifalar" },
{ prefiks: "/teglar", voris: "/v1/teglar" },
];
function sarlavhalar(url, yollar) {
const yol = url.split("?")[0] ?? "";
const y = yollar.find(
(e) => yol === e.prefiks || yol.startsWith(e.prefiks + "/"),
);
if (y === undefined) return null;
const qolgan = yol.slice(y.prefiks.length);
return {
Deprecation: "@1792800000",
Link: `<${y.voris}${qolgan}>; rel="successor-version"`,
};
}
const urllar = [
"/vazifalar/7?x=1", "/teglar", "/vazifalarim", "/v1/vazifalar",
];
for (const url of urllar) {
console.log(url, JSON.stringify(sarlavhalar(url, ESKI_YOLLAR)));
}Konsolda:
/vazifalar/7?x=1 {"Deprecation":"@1792800000","Link":"</v1/vazifalar/7>; rel=\"successor-version\""}
/teglar {"Deprecation":"@1792800000","Link":"</v1/teglar>; rel=\"successor-version\""}
/vazifalarim null
/v1/vazifalar nullFunksiya url ni oladi — req.originalUrl ni. Agar unga req.path berilsa va middleware '*splat' bilan ulangan bo'lsa, url doim / bo'lardi va har qatorda null — darsdagi buzilishning aynan o'zi.
9. Loyiha qadami: idempotentlik va eskirgan — Nest middleware'lari
9.1 Nima o'zgaradi
| Fayl | O'zgarish |
|---|---|
src/tokenlar.ts |
IDEMPOTENTLIK_OMBORI tokeni |
src/bogliqliklar.module.ts |
ombor — useValue provider |
src/umumiy/idempotentlik.middleware.ts |
yangi: klass middleware |
src/vazifalar/vazifalar.module.ts |
configure — faqat vazifalar controller'iga |
src/app.module.ts |
configure — eskirgan hamma yo'lga |
src/umumiy/eskirgan.ts |
req.path → req.originalUrl |
src/ilova.ts |
ikki app.use olib tashlanadi, ombor forRoot ga |
test/marshrutlar.test.ts |
forRoot ga ombor |
25-qismdagi idempotentlik.ts (middleware mantiqi) o'zgarmaydi — biz uni faqat Nest klassiga o'raymiz.
9.2 Token va provider
src/tokenlar.ts da IDEMPOTENTLIK_OMBORI tokeni bo'lishi kerak — bo'lmasa qo'shing:
export const IDEMPOTENTLIK_OMBORI = Symbol("IDEMPOTENTLIK_OMBORI");src/bogliqliklar.module.ts — tashqarida yaratilgan obyektlar DI'ga useValue bilan beriladi (Custom provider'lar). Ombor ham shular qatoriga qo'shiladi. Kanon faylidagi uzun qatorlarni bo'ldik (ma'no aynan):
// bogliqliklar.module.ts — tashqarida yaratilgan obyektlar (log,
// xabarchi, config, idempotentlik ombori) DI konteyneriga
// beriladi: useValue provider'lar. @Global — har modul import
// qilmasdan ishlatadi; forRoot(...) — dinamik modul
import { Global, Module } from "@nestjs/common";
import type { DynamicModule, Provider } from "@nestjs/common";
import {
CONFIG, IDEMPOTENTLIK_OMBORI, ILOVA_SOZLAMALARI, LOG, XABARCHI,
} from "./tokenlar.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import type { IlovaSozlamalari } from "./tokenlar.ts";
import type { Config } from "./config.ts";
import type { Log } from "./umumiy/log.ts";
import type { Xabarchi } from "./umumiy/webhook.ts";
export type TashqiBogliqliklar = {
log: Log;
xabarchi?: Xabarchi | undefined;
sozlamalar: IlovaSozlamalari;
config?: Config | undefined;
idempotentlikOmbori: IdempotentlikOmbori;
};
@Global()
@Module({})
export class BogliqliklarModule {
static forRoot({
log, xabarchi, sozlamalar, config, idempotentlikOmbori,
}: TashqiBogliqliklar): DynamicModule {
const providers: Provider[] = [
{ provide: LOG, useValue: log },
// xabarchi ixtiyoriy: yo'q bo'lsa null (xizmat @Optional bilan)
{ provide: XABARCHI, useValue: xabarchi ?? null },
{ provide: ILOVA_SOZLAMALARI, useValue: sozlamalar },
{
provide: IDEMPOTENTLIK_OMBORI,
useValue: idempotentlikOmbori,
},
...(config === undefined
? [] : [{ provide: CONFIG, useValue: config }]),
];
return {
module: BogliqliklarModule,
providers,
exports: providers.map(
(p) => (p as { provide: symbol }).provide,
),
};
}
}9.3 src/umumiy/idempotentlik.middleware.ts
// idempotentlik.middleware.ts — 25-qism Express middleware'i
// (idempotentlik.ts) Nest klass middleware'iga o'raladi: ombor
// DI'dan (IDEMPOTENTLIK_OMBORI tokeni — testda soxta soatli ombor,
// 27-qismda Redis), ulanish — VazifalarModule.configure()
import { Inject, Injectable } from "@nestjs/common";
import type { NestMiddleware } from "@nestjs/common";
import type {
NextFunction, Request, RequestHandler, Response,
} from "express";
import { idempotentlik } from "./idempotentlik.ts";
import type { IdempotentlikOmbori } from "./idempotentlik.ts";
import { IDEMPOTENTLIK_OMBORI } from "../tokenlar.ts";
/**
* Nest 12.2 TOPILMASI: forRoutes(VazifalarController) har marshrut
* uchun alohida ro'yxatdan o'tkazadi; Express use() prefiks bo'yicha
* moslaydi — POST /v1/vazifalar uchun middleware IKKI marta ishladi
* ("" POST va @All "" marshrutlari) va ikkinchi chaqiruv
* birinchisining "jarayonda" yozuvini ko'rib 409 qaytardi.
* Shuning uchun: bir so'rovga bir marta
*/
const ishlangan = new WeakSet<Request>();
@Injectable()
export class IdempotentlikMiddleware implements NestMiddleware {
readonly #ishlovchi: RequestHandler;
constructor(
@Inject(IDEMPOTENTLIK_OMBORI) ombor: IdempotentlikOmbori,
) {
this.#ishlovchi = idempotentlik(ombor);
}
// Promise qaytariladi — Express 5 rad etilgan Promise'ni next(err)
// ga yuboradi (409/422/400 → Problem Details filtri)
use(req: Request, res: Response, next: NextFunction): unknown {
if (ishlangan.has(req)) {
next();
return undefined;
}
ishlangan.add(req);
return this.#ishlovchi(req, res, next);
}
}- Constructor —
@Inject(IDEMPOTENTLIK_OMBORI):IdempotentlikOmbori— interfeys, ish vaqtida yo'q, shuning uchun token (DI va IoC konteyner). 25-qismdagiidempotentlik(ombor)bir marta chaqiriladi va tayyor Express middleware'i#ishlovchida saqlanadi. ishlangan— modul darajasidagiWeakSet: «Bir so'rovga bir marta» mashqidagi naqsh.return this.#ishlovchi(...)— 25-qism middleware'iasync; xato tashlasa (409, 422, 400) rad etilgan Promise qaytadi. Express 5 uni ushlabnext(err)qiladi (Express 5: async xatolar) va xato filtrga yetadi.
9.4 Ulash
src/vazifalar/vazifalar.module.ts:
// vazifalar.module.ts — vazifalar xususiyat moduli: controller'lar
// + xizmat. configure() — Idempotency-Key middleware'i FAQAT shu
// controller marshrutlariga (25 dagi v1.use("/vazifalar", …))
import { Module } from "@nestjs/common";
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
import { VazifalarController } from "./vazifalar.controller.ts";
import { TeglarController } from "./teglar.controller.ts";
import { VazifalarService } from "./vazifalar.service.ts";
import {
IdempotentlikMiddleware,
} from "../umumiy/idempotentlik.middleware.ts";
@Module({
controllers: [VazifalarController, TeglarController],
providers: [VazifalarService],
// boshqa modul (masalan eksport) xizmatdan foydalanmoqchi bo'lsa
exports: [VazifalarService],
})
export class VazifalarModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer.apply(IdempotentlikMiddleware)
.forRoutes(VazifalarController);
}
}forRoutes(VazifalarController) — TeglarController emas: 25-qismda ham idempotentlik faqat vazifalar to'plamida edi. Versiyali (/v1/vazifalar) va eskirgan (/vazifalar) yo'llar ikkalasi ham qamraladi — controller ikkala yo'lda ro'yxatdan o'tgan.
src/app.module.ts — AppModule ham NestModule bo'ladi (importlarga MiddlewareConsumer, NestModule, eskirgan, ESKI_YOLLAR qo'shiladi):
@Module({})
export class AppModule implements NestModule {
static forRoot(/* … o'zgarishsiz … */) { /* … */ }
// Funksional middleware: eski versiyasiz yo'llar ishlaydi, lekin
// Deprecation + Sunset + voris havolasi bilan — hamma yo'lga
// ("*splat" — Express 5 sintaksisi; eskirgan() o'zi /vazifalar,
// /teglar prefiksini tekshiradi)
configure(consumer: MiddlewareConsumer): void {
consumer.apply(eskirgan(ESKI_YOLLAR)).forRoutes("*splat");
}
}9.5 eskirgan.ts va ilova.ts
src/umumiy/eskirgan.ts da bitta qaror o'zgaradi — yo'l req.originalUrl dan:
export function eskirgan(
yollar: readonly EskiYol[],
): RequestHandler {
return (req, res, next) => {
- const y = yollar.find((e) => req.path === e.prefiks
- || req.path.startsWith(e.prefiks + "/"));
+ // req.path EMAS: yo'l bilan ulanganda (forRoutes("*splat"))
+ // Express ulangan qismni kesadi — req.path "/" bo'ladi
+ const yol = req.originalUrl.split("?")[0] ?? "";
+ const y = yollar.find((e) => yol === e.prefiks
+ || yol.startsWith(e.prefiks + "/"));
if (y !== undefined) {
…
- res.append("Link",
- `<${y.voris}${req.path.slice(y.prefiks.length)}>; …`);
+ res.append("Link",
+ `<${y.voris}${yol.slice(y.prefiks.length)}>; …`);src/ilova.ts da ikki app.use va ikki import o'chadi, ombor forRoot ga uzatiladi:
-import { idempotentlik, XotiraIdempotentlikOmbori }
- from "./umumiy/idempotentlik.ts";
+import { XotiraIdempotentlikOmbori }
+ from "./umumiy/idempotentlik.ts";
-import { eskirgan, ESKI_YOLLAR } from "./umumiy/eskirgan.ts";
…
- AppModule.forRoot({
- ombor, config, log, xabarchi, sozlamalar: { tz },
- })
+ AppModule.forRoot({
+ ombor, config, log, xabarchi, idempotentlikOmbori,
+ sozlamalar: { tz },
+ })
…
- app.use(eskirgan(ESKI_YOLLAR));
- app.use(["/v1/vazifalar", "/vazifalar"],
- idempotentlik(idempotentlikOmbori));
+ // Idempotency-Key va eskirgan yo'l sarlavhalari — endi Nest
+ // middleware'lari (modullarning configure()): DI bilanidempotentlikOmbori — ilovaYarat parametri (sukut — new XotiraIdempotentlikOmbori()); testlar o'z soxta soatli omborini beradi. test/marshrutlar.test.ts AppModule.forRoot ni to'g'ridan chaqiradi — endi ombor majburiy:
+import { XotiraIdempotentlikOmbori }
+ from "../src/umumiy/idempotentlik.ts";
…
AppModule.forRoot({
ombor: new XotiraOmbori(), log: logYarat("silent"),
+ idempotentlikOmbori: new XotiraIdempotentlikOmbori(),
sozlamalar: { tz: "Asia/Tashkent" },
})9.6 Tekshiruv — va tuzatishlarsiz nima bo'lardi
npm run tip, npm run lint va npm test toza o'tishi kerak (Postgres'siz yurgizsangiz: Tests 75 passed | 6 skipped (81) — bazali testlar SKIP).
Endi ikki tuzatishning qiymatini ko'raylik. Kanonning shu bosqichdagi nusxasida WeakSet tekshiruvini o'chirdik va testlarni yurgizdik (biz ishga tushirdik, Vitest 5.0.5, Postgres'siz; chiqishni qisqartirdik):
❯ test/mock.test.ts (5 tests | 1 failed) 1812ms
× stub (vi.spyOn): ombor.yarat bir marta chaqirildi — Idempotency-Key takrorida ombor CHAQIRILMAYDI 111ms
❯ test/idempotentlik.test.ts (5 tests | 4 failed) 1832ms
× takroriy POST bir xil kalit bilan — bitta vazifa, ikkinchisi saqlangan javob (Idempotency-Replayed) 547ms
× parallel: bir xil kalit — biri 201, ikkinchisi 409 + Retry-After 57ms
× xato javobi (422) ham saqlanadi; muddati o'tgach kalit qayta ishlaydi 16ms
× regressiya (…): mijoz kutmay uzilsa ham qayta urinish ikkinchi vazifa yaratmaydi 20ms
FAIL test/idempotentlik.test.ts > takroriy POST bir xil kalit bilan — bitta vazifa, ikkinchisi saqlangan javob (Idempotency-Replayed)
AssertionError: Expected values to be strictly equal:
409 !== 201
Test Files 2 failed | 16 passed | 5 skipped (23)
Tests 5 failed | 70 passed | 6 skipped (81)Tarjimasi: "kutilgan 201, keldi 409". Birinchi POST — kalit bilan, birinchi marta — 409 oldi: «Ikki marta ishlash» bo'limidagi diagramma aynan. Parallel testda ikkala so'rov 409 oldi, vazifa umuman yaratilmadi.
Keyin WeakSet ni qaytarib, eskirgan.ts da originalUrl o'rniga yana req.path qo'ydik:
❯ test/shartli.test.ts (3 tests | 1 failed) 2445ms
× versiyasiz eski yo'l ishlaydi, lekin Deprecation, Sunset va voris havolasi bilan 66ms
FAIL test/shartli.test.ts > versiyasiz eski yo'l ishlaydi, lekin Deprecation, Sunset va voris havolasi bilan
AssertionError: Expected values to be strictly equal:
+ actual - expected
+ null
- '@1792800000'
❯ test/shartli.test.ts:52:10
50| const r = await fetch(`${asos}/vazifalar/1`);
51| assert.equal(r.status, 200);
52| assert.equal(r.headers.get("deprecation"), "@1792800000");r.status — 200 (oldingi qator o'tdi), deprecation sarlavhasi — null. Mijoz "hammasi joyida" deb o'ylaydi, eskirgan yo'ldan ko'chish kerakligini bilmaydi.
Ikkala xato ham 25–27-qismlarning o'zgarmagan testlari bilan ushlandi: Express'dan Nest'ga ko'chishda testlar — xulq o'zgarmaganining yagona kafolati.
Hali commit qilmang — loyihaning bu bosqichi Global enhancer'lar va bajarilish tartibi darsida commit bilan yopiladi.
10. Real ishda
- Nest loyihalarida klass middleware ko'pincha log, so'rov konteksti, tenant aniqlash uchun; xavfsizlik tekshiruvlari esa guard'da. "Middleware'da auth" — Express'dan ko'chgan loyihalarning belgisi.
- Ikki marta ishlash tuzog'i haqida Nest hujjatida ogohlantirish yo'q (2026-11 holatida) — uni bilish va test bilan himoyalanish sizning ishingiz.
- Express'dan ko'chirishda eng xavfsiz yo'l — avval hamma middleware'ni
app.use()da o'zgarishsiz qoldirish, keyin bittalab modulga o'tkazish va har qadamda eski testlarni yurgizish. Biz ham shunday qildik: ikki tuzoqni aynan o'sha testlar ushladi. - Intervyu: "Middleware handler'ni biladimi?" (yo'q — shuning uchun rol tekshiruvi guard'da), "Nest'da middleware va guard farqi?", "middleware'ga DI qanday beriladi?", "
forRoutesga nima berish mumkin?".
Xulosa
- Nest middleware — Express middleware'i: funksional (
(req, res, next)) yoki klass (NestMiddleware.use, DI bilan). - Ulash —
NestModule.configure(consumer):apply(...).exclude(...).forRoutes(...); controller, yo'l yoki{ path, method }. app.use()— har so'rov (404 ham) va modul middleware'laridan oldin; modul middleware — faqat o'z marshrutlariga.- Tuzoqlar:
forRoutes(Controller)— bir yo'lda bir nechta metod bo'lsa middleware ikki marta (WeakSethimoyasi); yo'l bilan ulangandareq.pathkesiladi (req.originalUrl). - Middleware handler'ni ham, uning natijasini ham bilmaydi: marshrut belgisiga bog'liq qaror — guard'da, javobni o'zgartirish — interceptor'da.
vazifalar-api: idempotentlik —VazifalarModuleda DI bilan, eskirgan —AppModule.configureda.
Keyingi dars: Guard'lar — CanActivate, ExecutionContext, true/false va xato tashlash farqi; JsonTanaGuard ning ichini va Reflector.createDecorator lint topilmasini ko'ramiz.
Manbalar
- NestJS hujjati — Middleware (
NestMiddleware,MiddlewareConsumer,forRoutes,exclude, functional middleware): https://docs.nestjs.com/middleware - Express 5 —
app.useyo'l bilan vareq.path/req.originalUrl: https://expressjs.com/en/5x/api.html#req.originalUrl - IETF — The Idempotency-Key HTTP Header Field (qoralama): https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/
- Kanon (IlmHamroh, 2026-10-27): idempotentlik va eskirgan middleware'larining Nest'ga ko'chishi, ikki topilma (testlar ushlagan), lifecycle tartibi. Demo (klass/funksional middleware,
exclude, ikki marta ishlash,req.path) va tuzatishlarsiz test yurgizish — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Express 5.3.0, Vitest 5.0.5, Node 24.21.0);excludetartibi xatosi —tsc, TypeScript 6.0.3.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!