Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. NestFactory.create
- 2.1 Nima qiladi
- 2.2 Yaratish sozlamalari
- 3. Global sozlamalar — haqiqiy tajriba
- 3.1 Tajriba sharti
- 3.2 bufferLogs va useLogger
- 3.3 setGlobalPrefix va exclude
- 3.4 enableCors
- 3.5 rawBody: true
- 3.6 listen(port, host)
- 3.7 enableShutdownHooks
- 4. Express middleware'lari Nest ilovasida
- 4.1 app.use — tartib saqlanadi
- 4.2 Versiyalash: bitta controller, ikki yo'l
- 5. Xato filtri va Nest'ning "tarjimasi"
- 5.1 Global filter
- 5.2 Tuzoq: Nest xatoni filtrdan oldin o'zgartiradi
- 5.3 JS modelida xato yo'li
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): qaysi sozlama?
- 2-mashq (o'rta): prefiks modeli
- 3-mashq (qiyin): URI versiyalash modeli
- 8. Loyiha qadami: Nest bootstrap va Express'ni o'chirish
- 8.1 Reja
- 8.2 app.module.ts
- 8.3 ilova.ts
- 8.4 problem.filter.ts va tana-xatosi.ts
- 8.5 main.ts va ishga-tushir.ts
- 8.6 Eski fayllar va testlar
- 8.7 Tekshiruv
- 8.8 Commit
- 9. Real ishda
- Xulosa
- Manbalar
NestJS bootstrap: main.ts, NestFactory.create va ilova sozlamalari
Qisqacha: Bootstrap — ilovani ishga tushirish:
main.tsdaNestFactory.create(AppModule, sozlamalar)modul daraxtini yig'adi, provider'larni yaratadi va marshrutlarni Express'ga ulaydi;app.listen(port, host)esa portni ochadi. Ikkalasi orasida global sozlamalar beriladi:setGlobalPrefix,enableCors,enableVersioning,useGlobalFilters, Express middleware'lari (app.use). Yaratish sozlamalari (rawBody,bufferLogs,bodyParser,logger) esacreatening ikkinchi argumentida. Bu darsdavazifalar-apito'liq Nest'ga o'tadi.
Bu darsda
NestFactory.createnima qilishini va uning asosiy sozlamalarini bilasiz.setGlobalPrefix,enableCors,rawBody,bufferLogs,listen(port, host)ni haqiqiy chiqish bilan ko'rasiz.- Express middleware'larini Nest ilovasiga tartib bilan ulaysiz va versiyalashni sozlaysiz.
- Nest Express xatolarini filtrdan oldin "o'zgartirib yuborishini" va uni qanday tuzatishni bilasiz.
vazifalar-apining eski Express fayllarini o'chirasiz, testlarni Vitest'ga o'tkazasiz va commit qilasiz.
Oldin bilishingiz kerak: Provider'lar va service'lar, Controller'lar va route dekoratorlari, Middleware va request lifecycle, Global error handler, API versiyalash va deprecation, Reverse proxy orqasida Node.
O'tgan darsda xizmatlar yozildi, lekin test Symbol(VAZIFA_OMBORI) topilmadi deb yiqildi: BogliqliklarModule daraxtga ulanmagan edi. Bu darsda ilovani yig'adigan joy — main.ts va ilova.ts — Nest'ga o'tadi. Dars oxirida Express routerlari o'chadi va 25–27 dagi testlar Nest ilovasini tekshiradi.
1. Nega bu kerak?
25-qismda ishga tushirish ikki faylda edi. ilova.ts Express ilovasini yig'ardi: middleware'lar, routerlar, 404, xato ishlovchi. server.ts esa config, ombor va serverni ulardi. Tartib qo'lda, har qadam ko'rinib turardi.
Nest'da marshrutlar va xizmatlar endi modul ichida. Lekin ularni kimdir ishga tushirishi kerak: modul daraxtini yig'ish, nusxalarni yaratish, marshrutlarni HTTP serverga ulash. Bu ish — bootstrap. Undan tashqari, ilovaning hammasiga tegishli sozlamalar bor: CORS, yo'l prefiksi, versiya, xato formati. Ular bitta modulga emas, butun ilovaga tegishli — shuning uchun bootstrap'da beriladi.
O'xshatish: to'yxona. Modullar — oshxona, zal, musiqa. Har biri o'z ishini biladi. Lekin eshikni kim ochadi, mehmonni qaysi eshikdan kiritadi, qaysi soatda boshlanadi — bu boshqaruvchining ishi. main.ts — o'sha boshqaruvchi.
2. NestFactory.create
2.1 Nima qiladi
const app = await NestFactory.create<NestExpressApplication>(
AppModule, { bodyParser: false, logger: ["error", "warn"] },
);Bitta chaqiruv ichida to'rt ish bo'ladi:
AppModuledan boshlabimportsdaraxti yig'iladi (Modullar).- Har provider uchun bog'liqliklar topiladi va nusxalar yaratiladi (Provider'lar).
- Controller metodlari Express marshrutlariga aylanadi (logdagi
Mapped {…}qatorlari). - Platform adapter (sukut — Express) tayyorlanadi.
flowchart TB
M["main.ts: config, omborYarat"] --> C["NestFactory.create"]
C --> D["modul daraxti, provider nusxalari"]
D --> G["global sozlamalar: app.use, versiya, filtr"]
G --> I["app.init: marshrutlar ulanadi"]
I --> L["app.listen(port, host)"]
L --> J["jarayonniBoshqar: signal, yopish"]Diagrammani yuqoridan o'qing: create ichida modullar va nusxalar paydo bo'ladi, global sozlamalar undan keyin beriladi, port esa eng oxirida ochiladi. Testlar listen gacha bo'lgan qismni ishlatadi.
Natija — ilova obyekti, lekin port hali ochilmagan. create dan keyin, listen dan oldin global sozlamalar beriladi. <NestExpressApplication> generigi — Express'ga xos metodlar uchun: app.set("trust proxy", …), app.disable("x-powered-by"), app.useStaticAssets(…). Usiz ular turda ko'rinmaydi.
2.2 Yaratish sozlamalari
Ikkinchi argument — faqat yaratish paytida ma'noli sozlamalar:
| Sozlama | Nima qiladi | vazifalar-api da |
|---|---|---|
logger |
Nest'ning o'z logi: false, darajalar ro'yxati yoki logger obyekti |
["error", "warn"] (testda false) |
bodyParser |
Nest o'z JSON/urlencoded parser'ini qo'yadimi | false — o'zimizniki |
rawBody |
req.rawBody da xom bayt (Buffer) saqlansinmi |
yo'q |
bufferLogs |
useLogger chaqirilguncha loglar kutsinmi |
yo'q |
cors |
enableCors ning qisqa shakli |
yo'q (cors paketi) |
abortOnError |
yaratishda xato bo'lsa jarayonni o'ldirsinmi | Lifecycle hook'lar darsida |
bodyParser: false ning sababi: 25-qismda JSON parser'ni o'zimiz sozlagandik — 16 KB limit va application/merge-patch+json turi (Request body o'qish va hajm cheklovi). Nest'ning o'z parser'i bunday sozlanmagan. Ikkita parser yonma-yon bo'lsa, birinchisi tanani o'qib oladi va bizning limit ishlamay qoladi. Shuning uchun Nest'nikini o'chiramiz va o'zimiznikini app.use(express.json(…)) bilan qo'yamiz.
3. Global sozlamalar — haqiqiy tajriba
3.1 Tajriba sharti
Xaritadagi bootstrap sozlamalarini bitta demo ilovada sinab ko'rdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Ikki controller — vazifalar (ichida xom tana ko'rsatadigan webhook) va salomatlik:
@Controller('vazifalar')
class VazifalarController {
@Get() royxat() {
return [];
}
@Post('webhook') webhook(@Req() req: RawBodyRequest<Request>) {
return {
rawBody: req.rawBody?.constructor.name,
uzunlik: req.rawBody?.length,
body: req.body as unknown,
};
}
}
const app = await NestFactory.create(AppModule, {
rawBody: true,
bufferLogs: true,
});
app.useLogger(new MeningLogim()); // har qatorga [mening] yorlig'i
app.setGlobalPrefix('api', { exclude: ['salomatlik'] });
app.enableCors({ origin: ['http://localhost:5173'] });
await app.listen(0, '0.0.0.0');3.2 bufferLogs va useLogger
Ishga tushish logi:
[mening] NestFactory: Starting Nest application...
[mening] InstanceLoader: AppModule dependencies initialized
[mening] RoutesResolver: VazifalarController {/api/vazifalar}:
[mening] RouterExplorer: Mapped {/api/vazifalar, GET} route
[mening] RouterExplorer: Mapped {/api/vazifalar/webhook, POST} route
[mening] RoutesResolver: SalomatlikController {/api/salomatlik}:
[mening] RouterExplorer: Mapped {/salomatlik, GET} route
[mening] NestApplication: Nest application successfully startedBirinchi qatorga qarang: Starting Nest application... ham bizning logger orqali chiqdi. Holbuki useLogger create dan keyin chaqirilgan. bufferLogs: true shuni qiladi: create paytidagi loglar xotirada kutadi va useLogger dan keyin yangi logger'ga beriladi. Usiz birinchi qatorlar Nest'ning sukut formatida chiqardi, keyingilari — sizning formatingizda. JSON log yig'adigan tizim uchun bu aralash qatorlar muammo (Pino chuqur). Nest logini pino'ga ulash — NestJS Logger va nestjs-pino darsida.
3.3 setGlobalPrefix va exclude
Logdagi Mapped qatorlarida /api prefiksi bor, salomatlik dan tashqari. So'rovlar:
GET /vazifalar → 404 (CORS: http://localhost:5173)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /vazifalar</pre>
</body>
</html>
GET /api/vazifalar → 200 (CORS: http://localhost:5173)
[]
GET /salomatlik → 200 (CORS: http://localhost:5173)
{"ok":true}setGlobalPrefix('api') hamma marshrut oldiga /api qo'shdi. exclude: ['salomatlik'] — istisno: salomatlik tekshiruvi prefikssiz qoldi. Orkestrator (Docker, Kubernetes) uni odatda ildiz yo'lda qidiradi.
Ikki kutilmagan narsa bor. Birinchisi — logdagi SalomatlikController {/api/salomatlik}: controller qatori prefiksni ko'rsatdi, haqiqiy marshrut esa /salomatlik. Ishonchli manba — Mapped qatorlari. Ikkinchisi muhimroq: GET /vazifalar ga javob HTML. Bu Nest'ning JSON 404 i emas, Express'ning sukut sahifasi. Nest o'z "topilmadi" ishlovchisini ham prefiks ostiga qo'yadi. Prefiksdan tashqaridagi so'rov Nest'ga umuman yetmaydi — uni Express o'zi yopadi. API mijozi JSON kutsa, bu sahifa uni chalg'itadi.
3.4 enableCors
Hamma javobda CORS: http://localhost:5173 — Access-Control-Allow-Origin sarlavhasi. app.enableCors(...) ichida aynan cors paketi ishlaydi (CORS chuqur). Sozlamalar ham bir xil: origin, allowedHeaders, exposedHeaders, maxAge. Shuning uchun vazifalar-api da 25-qismdagi app.use(cors({...})) qatori o'zgarmay qoladi — natija bir xil, kod esa tanish.
3.5 rawBody: true
POST /api/vazifalar/webhook → 201 (CORS: http://localhost:5173)
{"rawBody":"Buffer","uzunlik":8,"body":{"id":7}}req.body — tahlil qilingan JSON, req.rawBody — o'sha tananing xom baytlari (Buffer, 8 bayt: {"id":7}). Nega ikkalasi kerak? Kiruvchi webhook imzosi xom baytlar ustida hisoblanadi (Kiruvchi webhook'larni qabul qilish). Agar JSON.parse dan keyin qayta JSON.stringify qilsangiz, bo'shliqlar va kalitlar tartibi o'zgarishi mumkin — imzo mos kelmaydi. rawBody faqat Nest'ning o'z parser'i bilan ishlaydi; bodyParser: false bo'lsa, xom tanani o'zingiz saqlaysiz.
3.6 listen(port, host)
getUrl: http://127.0.0.1:<port>app.listen(0, '0.0.0.0') — 0 port: "bo'sh portni o'zing tanla" (testlarda qulay). '0.0.0.0' — barcha tarmoq interfeyslarida tinglash. getUrl() baribir 127.0.0.1 ko'rsatdi — bu faqat ko'rsatish uchun manzil.
'0.0.0.0' va '127.0.0.1' farqi katta. Birinchisida port tashqi tarmoqdan ham ochiq. Ikkinchisida faqat shu kompyuter ichidan. Qoida Reverse proxy orqasida Node darsidan: proxy orqasidagi Node porti tashqariga ochilmasin — 127.0.0.1. Bizning API sukut bo'yicha shunday (HOST sozlamasi). 0.0.0.0 konteyner ichida kerak bo'ladi — u yerda tashqi dunyo bilan konteyner tarmog'i ajratib turadi (Docker — 33-qismda).
Tekshirib ko'ring:
setGlobalPrefix('api')dan keyin mijoz eski/vazifalaryo'liga so'rov yubordi. U qanday javob oladi va nega bu yomon?
Javob
404 va HTML sahifa (Cannot GET /vazifalar). Nest'ning JSON xato ishlovchisi prefiks ostida — prefiksdan tashqaridagi so'rov unga yetmaydi, Express'ning sukut javobi chiqadi. JSON kutgan mijoz JSON.parse da yiqiladi, Problem Details formati ham yo'q. Shuning uchun vazifalar-api eski yo'llar uchun prefiks emas, versiyalash ishlatadi (keyingi bo'lim).
3.7 enableShutdownHooks
2-darsdagi skelet main.ts da app.enableShutdownHooks() bor edi. U Nest'ga SIGTERM va SIGINT signallarini tinglashni buyuradi: signal kelsa, Nest app.close() ni o'zi chaqiradi va modullarning yopish hook'lari ishlaydi.
vazifalar-api da bu qator bu darsdan boshlab yo'q. 25-qismda signal va drenaj uchun o'z kodimiz bor — jarayon.ts va yop() (Process darajasidagi xatolar va crash strategiyasi). Ikkalasi bir vaqtda signal tinglasa, yopish ikki marta boshlanadi. Nest'ning yopishi bajarilayotgan so'rov bor paytda qanday ishlashini kanon o'lchagan — natijasi va qarorning to'liq sababi Lifecycle hook'lar darsida.
4. Express middleware'lari Nest ilovasida
4.1 app.use — tartib saqlanadi
Nest ilovasi ichida Express ishlaydi. Shuning uchun 25-qismdagi middleware'lar o'zgarmasdan ulanadi:
app.use(sorovId());
app.use(sorovLogi(log));
app.use(helmet({ strictTransportSecurity: hsts }));
app.use(cors({ /* … */ }));
app.useStaticAssets(join(import.meta.dirname, "..", "public"));
app.use(express.json({ limit: "16kb", type: JSON_TURLARI }));
app.use(tanaXatosi);
app.use(eskirgan(ESKI_YOLLAR));app.use bilan qo'yilgan middleware Nest marshrutlaridan oldin ishlaydi va yozilgan tartibda. Tartib 25-qismdagidek: so'rov ID'si → log va AsyncLocalStorage konteksti → xavfsizlik sarlavhalari → CORS → statik fayllar → JSON tana (Middleware va request lifecycle). useStaticAssets — Express'dagi express.static ning Nest shakli. Nest'ning o'z middleware tizimi ham bor — Middleware Nest'da darsida.
4.2 Versiyalash: bitta controller, ikki yo'l
25-qismda v1 router ikki joyga ulangan edi: /v1 va eskirgan versiyasiz yo'l. Nest'da bu bitta sozlama:
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: ["1", VERSION_NEUTRAL],
});type: VersioningType.URI— versiya yo'lning o'zida:/v1/vazifalar. Boshqa turlar ham bor: sarlavha (HEADER),Acceptturi (MEDIA_TYPE), o'zingizning funksiyangiz (CUSTOM).defaultVersion: ["1", VERSION_NEUTRAL]— versiyasi ko'rsatilmagan controller ikki versiyaga tegishli:1va "versiyasiz". Natijada@Controller("vazifalar")ham/v1/vazifalarda, ham/vazifalarda javob beradi.
Eskirgan yo'lga Deprecation va Sunset sarlavhalari — eskirgan(ESKI_YOLLAR) middleware'i, 25-qismdagidek. Kelajakda v2 paydo bo'lsa, yangi controller @Controller({ path: "vazifalar", version: "2" }) deb yoziladi.
Nega setGlobalPrefix("v1") emas? Prefiks hamma yo'lni almashtiradi — eski /vazifalar yo'qoladi va mijozlar Express'ning HTML 404 ini oladi (yuqoridagi tajriba). Versiyalash esa ikkala yo'lni bitta controller'ga yo'naltiradi.
5. Xato filtri va Nest'ning "tarjimasi"
5.1 Global filter
25-qismdagi xatoIshlovchi — oxirgi, 4 argumentli Express middleware edi. Nest'da uning o'rni — exception filter (xato filtri): @Catch() dekoratorli klass, uni app.useGlobalFilters(new ProblemDetailsFilter(…)) bilan ulaymiz. U hamma xatoni bir joyda Problem Details'ga aylantiradi (Xato javoblari: Problem Details). Filtrlar haqida to'liq — Exception filter'lar darsida. Bu darsda kanon filtrini o'qiymiz va bitta tuzoqni ko'ramiz.
Filtr Nest'ning o'z 404 ini ham ushlaydi. NestJS nima va nega darsida sukut javob {"message":"Cannot GET /yoq","error":"Not Found","statusCode":404} edi. Filtr bilan:
$ curl /yoq
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8
{"type":"about:blank","title":"Not Found","status":404,"detail":"GET /yoq — bunday manzil yo'q","instance":"/yoq",…}5.2 Tuzoq: Nest xatoni filtrdan oldin o'zgartiradi
Kanon ko'chish paytida g'alati narsani topdi. Yaroqsiz JSON yuborilganda 25-qismda javob "detail":"Tana yaroqli JSON emas" edi. Nest'da esa bu xabar chiqmadi.
Sabab: body-parser xatoni http-errors obyekti sifatida beradi — unda status, expose va type: "entity.parse.failed" maydonlari bor. Nest esa Express xatosini filtrga uzatishdan oldin "xaritalaydi": SyntaxError ni BadRequestException(err.message) ga aylantiradi. type maydoni yo'qoladi. Filtr endi "bu JSON xatosi" ekanini bilmaydi.
Biz buni o'zimiz tekshirdik: tanaXatosi qatorini vaqtincha o'chirib, ikki so'rov yubordik (biz ishga tushirdik, SAQLASH=xotira). Yaroqsiz JSON ({"matn":):
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json; charset=utf-8
{"type":"about:blank","title":"Bad Request","status":400,"detail":"Unexpected end of JSON input","instance":"/v1/vazifalar",…}20 000 belgili matn (16 KB dan katta):
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json; charset=utf-8
{"type":"about:blank","title":"Internal Server Error","status":500,"detail":"Kutilmagan xato. sorovId bilan murojaat qiling.",…}Birinchisida parser'ning inglizcha ichki xabari mijozga chiqdi. Ikkinchisi yomonroq: 413 bo'lishi kerak edi, 500 chiqdi. Katta tana xatosi SyntaxError emas — Nest uni xaritalamadi, u HttpException ham emas — filtr uni "kutilmagan xato" deb oldi. Mijoz "server buzildi" deb o'ylaydi, aslida u o'zi katta tana yubordi.
flowchart LR
P["express.json: xato"] --> T{"tanaXatosi bormi?"}
T -- yo'q --> N["Nest: SyntaxError → 400, qolgani o'zgarmaydi"]
N --> F1["filtr: inglizcha xabar yoki 500"]
T -- ha --> X["IlovaXatosi(status, o'zbekcha)"]
X --> F2["filtr: 400 / 413 aniq"]Diagrammadagi ikki yo'lni solishtiring: pastki yo'lda xato Nest'ga yetganda allaqachon bizning turimizda.
Yechim — Nest'gacha ushlash. tanaXatosi — express.json dan keyin turadigan 4 argumentli Express middleware. U http-errors ni IlovaXatosi ga aylantiradi, type ma'lumoti yo'qolishidan oldin. Shu qator bilan:
HTTP/1.1 400 Bad Request
{"type":"about:blank","title":"Bad Request","status":400,"detail":"Tana yaroqli JSON emas",…}
HTTP/1.1 413 Payload Too Large
{"type":"about:blank","title":"Payload Too Large","status":413,"detail":"Tana 16 KB dan katta",…}5.3 JS modelida xato yo'li
Xato qaysi yo'ldan o'tishini kichik modelda ko'raylik. Bu blok brauzerda ishlaydi. nestXaritasi — Nest'ning SyntaxError ni o'zgartiradigan qadami (soddalashtirilgan), filtr — bizning filtrning qisqa shakli:
const TANA = {
"entity.parse.failed": "Tana yaroqli JSON emas",
"entity.too.large": "Tana 16 KB dan katta",
};
function httpXato(Klass, status, type, xabar) {
const e = new Klass(xabar);
Object.assign(e, { status, type, expose: true });
return e;
}
class IlovaXatosi extends Error {
constructor(holat, xabar) { super(xabar); this.holat = holat; }
}
class BadRequest extends Error { holat = 400; }
const nestXaritasi = (x) =>
x instanceof SyntaxError ? new BadRequest(x.message) : x;
const filtr = (x) =>
x instanceof IlovaXatosi || x instanceof BadRequest
? `${x.holat} ${x.message}`
: "500 Kutilmagan xato";
const tanaXatosi = (x) =>
typeof x.status === "number" && x.expose
? new IlovaXatosi(x.status, TANA[x.type] ?? x.message)
: x;
const xatolar = [
httpXato(SyntaxError, 400, "entity.parse.failed",
"Unexpected end of JSON input"),
httpXato(Error, 413, "entity.too.large",
"request entity too large"),
];
for (const x of xatolar) {
console.log("tanaXatosi'siz: ", filtr(nestXaritasi(x)));
const tuzatilgan = nestXaritasi(tanaXatosi(x));
console.log("tanaXatosi bilan:", filtr(tuzatilgan));
}Konsolda:
tanaXatosi'siz: 400 Unexpected end of JSON input
tanaXatosi bilan: 400 Tana yaroqli JSON emas
tanaXatosi'siz: 500 Kutilmagan xato
tanaXatosi bilan: 413 Tana 16 KB dan kattaTo'rt qator — yuqoridagi to'rt haqiqiy javobning qisqa shakli. Model nimani soddalashtirgan: haqiqiy Nest BadRequestException ni @nestjs/common dan oladi va filtr Problem Details tanasini yasaydi. G'oya esa aynan: xato Nest'ga yetmasdan oldin tarjima qilinsa, ma'lumot yo'qolmaydi.
Tekshirib ko'ring: Nega
tanaXatosiniapp.useGlobalFiltersga yangi filtr qilib qo'shish yechim emas?
Javob
Filtr Nest xaritalagandan keyin ishlaydi — u allaqachon BadRequestException ni yoki type siz xatoni oladi. Ma'lumot filtrgacha yo'qoladi. Shuning uchun tarjima Express darajasida, express.json dan darhol keyin, xato Nest'ga uzatilishidan oldin bo'lishi kerak.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| 16 KB limit ishlamaydi | Nest parser'i ham yoqiq | bodyParser: false + o'z express.json |
| Prefiksdan tashqari yo'lga HTML 404 | setGlobalPrefix — Nest 404 i ham prefiks ostida |
exclude yoki versiyalash |
| Katta tana — 500 | http-errors Nest'ga xom yetdi |
tanaXatosi kabi tarjima middleware'i |
app.set/useStaticAssets turda yo'q |
generik berilmagan | create<NestExpressApplication> |
| Server tashqaridan ochiq | listen(port, "0.0.0.0") proxy orqasida |
127.0.0.1 (konteynerdan tashqarida) |
| Logning birinchi qatorlari boshqa formatda | bufferLogs yo'q |
bufferLogs: true + useLogger |
7. Mashqlar
Mashqlar kurs/mashqlar/30/06-bootstrap/ da.
1-mashq (oson): qaysi sozlama?
- Webhook imzosi uchun xom tana —
NestFactory.create(AppModule, { : true }) - Hamma yo'l oldiga
/api—app.("api") - Konteyner ichida hamma interfeysda tinglash —
app.listen(3000, "") - Nest'ning o'z JSON parser'ini o'chirish —
{ : false } /v1/vazifalarva/vazifalar— bitta controller:defaultVersion: ["1", ]
Yechim
- rawBody —
req.rawBodyda Buffer. - setGlobalPrefix —
excludebilan istisno qo'shiladi. - 0.0.0.0 — faqat konteyner yoki ishonchli tarmoqda; oddiy serverda proxy orqasida
127.0.0.1. - bodyParser —
vazifalar-apida o'z parser'imiz uchun. - VERSION_NEUTRAL — "versiyasiz" ham shu controller'ga.
2-mashq (o'rta): prefiks modeli
prefiksQosh(marshrutlar, prefiks, istisno) funksiyasini yozing: har marshrut yo'li oldiga /${prefiks} qo'shsin, istisno ro'yxatidagilardan tashqari. Keyin top(jadval, url) — mos yo'l bo'lsa "200 <yo'l>", bo'lmasa: url prefiks bilan boshlansa "404 JSON (Nest)", aks holda "404 HTML (Express)". Sinov: /vazifalar, /salomatlik (istisno), so'rovlar /vazifalar, /api/vazifalar, /salomatlik, /api/yoq. Ishora: startsWith (satr metodlari), includes.
Yechim
function prefiksQosh(marshrutlar, prefiks, istisno) {
return marshrutlar.map((y) =>
istisno.includes(y) ? y : `/${prefiks}${y}`);
}
function top(jadval, url, prefiks) {
if (jadval.includes(url)) return `200 ${url}`;
return url.startsWith(`/${prefiks}/`)
? "404 JSON (Nest)"
: "404 HTML (Express)";
}
const jadval = prefiksQosh(
["/vazifalar", "/salomatlik"], "api", ["/salomatlik"],
);
console.log(jadval.join(", "));
for (const url of
["/vazifalar", "/api/vazifalar", "/salomatlik", "/api/yoq"]) {
console.log(url, "→", top(jadval, url, "api"));
}Konsolda:
/api/vazifalar, /salomatlik
/vazifalar → 404 HTML (Express)
/api/vazifalar → 200 /api/vazifalar
/salomatlik → 200 /salomatlik
/api/yoq → 404 JSON (Nest)Model darsdagi tajribani takrorlaydi: prefiksdan tashqaridagi yo'l Nest'ning JSON 404 iga yetmaydi. /api/yoq esa prefiks ostida — u Nest'ning ishlovchisiga tushadi. Haqiqiy Nest'da 404 javobining shakli global filtrga bog'liq; vazifalar-api da u Problem Details.
3-mashq (qiyin): URI versiyalash modeli
versiyaTop(url, controllerlar) funksiyasini yozing. Har controller: { yol: "vazifalar", versiyalar: ["1", "neytral"] }. URL /v<N>/<yol>… shaklida bo'lsa — versiya N, aks holda — "neytral". Mos controller topilsa "<yol> v<N>" yoki "<yol> versiyasiz (eskirgan)" qaytarsin, topilmasa "404". Sinov: /v1/vazifalar/5, /vazifalar/5, /v2/vazifalar, /v2/teglar (teglar — ["1", "2"]). Ishora: /^\/v(\d+)(\/.*)$/ (RegExp), split("/").
Yechim
function versiyaTop(url, controllerlar) {
const m = /^\/v(\d+)(\/.*)$/.exec(url);
const versiya = m ? m[1] : "neytral";
const qolgan = m ? m[2] : url;
const yol = qolgan.split("/")[1];
const c = controllerlar.find(
(k) => k.yol === yol && k.versiyalar.includes(versiya),
);
if (!c) return "404";
return versiya === "neytral"
? `${yol} versiyasiz (eskirgan)`
: `${yol} v${versiya}`;
}
const controllerlar = [
{ yol: "vazifalar", versiyalar: ["1", "neytral"] },
{ yol: "teglar", versiyalar: ["1", "2"] },
];
const urllar = [
"/v1/vazifalar/5", "/vazifalar/5", "/v2/vazifalar", "/v2/teglar",
];
for (const url of urllar) {
console.log(url, "→", versiyaTop(url, controllerlar));
}Konsolda:
/v1/vazifalar/5 → vazifalar v1
/vazifalar/5 → vazifalar versiyasiz (eskirgan)
/v2/vazifalar → 404
/v2/teglar → teglar v2"neytral" — Nest'dagi VERSION_NEUTRAL ning o'rni. /v2/vazifalar — 404: bunday versiya yo'q. Haqiqiy Nest bundan ko'proq ishlaydi (sarlavha va media turi bo'yicha versiya, bitta metodga alohida versiya), lekin URI rejimining mantig'i shu.
8. Loyiha qadami: Nest bootstrap va Express'ni o'chirish
8.1 Reja
Bu dars — ko'chishning oxirgi qismi. Uch ish:
- Ilovani yig'ish Nest'ga o'tadi:
app.module.ts,ilova.ts,main.ts,ishga-tushir.ts, yangiproblem.filter.tsvatana-xatosi.ts. - Eski Express fayllari o'chadi.
- 25–27 testlari Vitest'ga o'tadi, tanalari o'zgarmaydi.
Kanon fayllarida qatorlar uzun edi — telefon uchun bo'ldik (izohlar qisqartirildi, kod ma'nosi aynan). Bo'lingan nusxani biz kanon nusxasida tsc --noEmit, ESLint, Vitest va build bilan tekshirdik.
8.2 app.module.ts
// app.module.ts — ildiz modul: ilova shu moduldan boshlab yig'iladi
// (imports daraxti). forRoot(...) — tashqarida (omborYarat)
// yaratilgan bog'liqliklarni global modul orqali beradi
import { Module } from "@nestjs/common";
import type { DynamicModule } from "@nestjs/common";
import { BogliqliklarModule } from "./bogliqliklar.module.ts";
import type { TashqiBogliqliklar } from "./bogliqliklar.module.ts";
import { VazifalarModule } from "./vazifalar/vazifalar.module.ts";
import { EksportModule } from "./eksport/eksport.module.ts";
@Module({})
export class AppModule {
static forRoot(b: TashqiBogliqliklar): DynamicModule {
return {
module: AppModule,
imports: [
BogliqliklarModule.forRoot(b),
VazifalarModule,
EksportModule,
],
};
}
}SalomController ketdi — skeletning vazifasi tugadi. forRoot(b) — 3-darsdagi "dinamik modul — bir qarash" ko'rinishi. U o'tgan darsdagi xatoni yopadi: endi BogliqliklarModule daraxtda va VAZIFA_OMBORI ko'rinadi.
8.3 ilova.ts
// ilova.ts — Nest ilovasi (25-qism ilova.ts ning o'rni): Express
// middleware'lari → Nest marshrutlari → Problem Details filtri.
// listen() YO'Q — server ishga-tushir.ts da, testlar ilovani portsiz
// ham ishlata oladi (supertest: app.getHttpServer())
import { NestFactory } from "@nestjs/core";
import { VERSION_NEUTRAL, VersioningType } from "@nestjs/common";
import type { LogLevel } from "@nestjs/common";
import type {
NestExpressApplication,
} from "@nestjs/platform-express";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { join } from "node:path";
import { AppModule } from "./app.module.ts";
import { sorovId } from "./umumiy/sorov-id.ts";
import {
idempotentlik, XotiraIdempotentlikOmbori,
} from "./umumiy/idempotentlik.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import { JSON_TURLARI } from "./umumiy/json-kerak.ts";
import { eskirgan, ESKI_YOLLAR } from "./umumiy/eskirgan.ts";
import { tanaXatosi } from "./umumiy/tana-xatosi.ts";
import { ProblemDetailsFilter } from "./umumiy/problem.filter.ts";
import { joriyLog, logYarat, sorovLogi } from "./umumiy/log.ts";
import type { Log } from "./umumiy/log.ts";
import type { Xabarchi } from "./umumiy/webhook.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
export type Bogliqliklar = {
ombor: VazifaOmbori;
/** CORS ruxsat etilgan origin'lar (frontend: Vite dev, Pages) —
* bo'sh massiv: CORS sarlavhasi yo'q */
corsOriginlar?: readonly string[];
/** Idempotency-Key javoblari */
idempotentlikOmbori?: IdempotentlikOmbori;
/** Pino logger (sukut — "silent": testlarda jim) */
log?: Log;
/** tashqi xabar (WEBHOOK_URL bo'lsa) */
xabarchi?: Xabarchi;
/** foydalanuvchi sanalari mintaqasi (?dan=, ?gacha=) */
tz?: string;
/** Express "trust proxy": false — proxy yo'q */
trustProxy?: boolean | number | string;
/** HSTS faqat HTTPS orqasida (proxy) yoqiladi */
hsts?: boolean;
/** Nest'ning o'z logger'i (bootstrap xabarlari) — sukut o'chiq;
* server ["error", "warn"] beradi */
nestLogi?: LogLevel[] | false;
};
export async function ilovaYarat({
ombor,
corsOriginlar = [],
idempotentlikOmbori = new XotiraIdempotentlikOmbori(),
log = logYarat("silent"),
xabarchi,
tz = "Asia/Tashkent",
trustProxy = false,
hsts = false,
nestLogi = false,
}: Bogliqliklar): Promise<NestExpressApplication> {
// bodyParser: false — Nest o'z JSON parser'ini qo'ymasin: biznikida
// limit (16 KB) va merge-patch+json turi bor
const app = await NestFactory.create<NestExpressApplication>(
AppModule.forRoot({ ombor, log, xabarchi, sozlamalar: { tz } }),
{ bodyParser: false, logger: nestLogi },
);
app.set("trust proxy", trustProxy);
app.disable("x-powered-by"); // "Express" ekanini oshkor qilmaymiz
// app.use() — Express middleware'lari, Nest marshrutlaridan OLDIN,
// shu tartibda (25-qism ilova.ts bilan bir xil)
app.use(sorovId());
// so'rov logi + AsyncLocalStorage konteksti (sorovId) — qolgan
// hamma middleware, guard, handler shu kontekst ichida
app.use(sorovLogi(log));
app.use(helmet({ strictTransportSecurity: hsts }));
app.use(
cors({
origin: [...corsOriginlar],
allowedHeaders: [
"Content-Type", "Idempotency-Key", "X-Request-Id", "If-Match",
"If-None-Match",
],
exposedHeaders: [
"Location", "X-Request-Id", "Idempotency-Replayed", "ETag",
"Link", "Deprecation", "Sunset",
],
maxAge: 600,
}),
);
app.useStaticAssets(join(import.meta.dirname, "..", "public"));
app.use(express.json({ limit: "16kb", type: JSON_TURLARI }));
app.use(tanaXatosi);
// Eski versiyasiz yo'llar — ishlaydi, lekin eskirgan: Deprecation +
// Sunset + vorisga havola
app.use(eskirgan(ESKI_YOLLAR));
// Idempotency-Key — faqat vazifalar to'plamida (25-qismdagidek);
// keyinroq Nest middleware'iga (MiddlewareConsumer) o'tadi
app.use(
["/v1/vazifalar", "/vazifalar"],
idempotentlik(idempotentlikOmbori),
);
// Versiya URI'da: /v1/... ; VERSION_NEUTRAL — versiyasiz eski
// yo'l ham shu controller'ga (Express'da v1 router ikki joyga
// ulangan edi)
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: ["1", VERSION_NEUTRAL],
});
app.useGlobalFilters(new ProblemDetailsFilter((_id, xato) => {
joriyLog(log).error({ err: xato }, "kutilmagan xato");
}));
await app.init();
return app;
}25-qismdagi bilan farqlar:
| 25–27 (Express) | Nest |
|---|---|
function ilovaYarat(...): Express |
async function …: Promise<NestExpressApplication> |
express() |
NestFactory.create(AppModule.forRoot(…), …) |
vazifaXizmati(ombor, …) |
DI (BogliqliklarModule) |
v1.use(...), app.use("/v1", v1) |
controller'lar + enableVersioning |
app.use(topilmadi) |
Nest router'ining 404 i → filtr |
app.use(xatoIshlovchi(...)) |
useGlobalFilters(new ProblemDetailsFilter(...)) |
app.init() — marshrutlar va middleware'lar shu yerda ulanadi. listen hali yo'q: testlar ilovani portsiz ishlatadi (app.getHttpServer()). ilovaYarat endi async — Nest yaratish Promise qaytaradi. Bu kichik farq testlarga ta'sir qiladi — «Eski fayllar va testlar» bo'limida.
8.4 problem.filter.ts va tana-xatosi.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…)
import {
Catch, HttpException, 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";
export type XatoLogi = (sorovId: string, xato: unknown) => void;
@Catch()
export class ProblemDetailsFilter implements ExceptionFilter {
constructor(
private readonly logla: XatoLogi = (id, x) => {
console.error(`[${id}]`, x);
},
) {}
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(req.sorovId, 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 bilan
// (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 (stack,
// xabar) mijozga BERILMAYDI — faqat logga
this.logla(req.sorovId, xato);
problemYubor(res, problemYasa(500, {
...umumiy,
detail: "Kutilmagan xato. sorovId bilan murojaat qiling.",
}));
}
}Mantiq 25-qismdagi xatoIshlovchi bilan bir xil, ikki qo'shimcha bilan: Nest'ning NotFoundException i va boshqa HttpException lar. host.switchToHttp() — filtr HTTP'dan boshqa kontekstda ham ishlay oladi (mikroservis, WebSocket), shuning uchun so'rov va javob shu orqali olinadi. Bu tafsilotlar — Exception filter'lar darsida.
// tana-xatosi.ts — Express xato middleware'i (4 argument):
// body-parser (express.json) va serve-static xatolarini IlovaXatosi
// ga aylantiradi. Nima uchun kerak: Nest Express xatolarini o'z
// filtriga uzatishdan oldin "xaritalaydi" — SyntaxError (yaroqsiz
// JSON) → BadRequestException(err.message), va body-parser'ning
// `type` maydoni ("entity.parse.failed") yo'qoladi. Shuning uchun
// xatoni Nest'dan OLDIN o'zimiz tarjima qilamiz
import type { ErrorRequestHandler } from "express";
import { IlovaXatosi } from "./xatolar.ts";
// body-parser xatolari (`type` maydoni) → o'zbekcha tafsilot
const TANA_XATOLARI: Record<string, string> = {
"entity.parse.failed": "Tana yaroqli JSON emas",
"entity.too.large": "Tana 16 KB dan katta",
"encoding.unsupported": "Kodlash qo'llab-quvvatlanmaydi",
"charset.unsupported": "Belgilar to'plami qo'llab-quvvatlanmaydi",
};
/** http-errors (body-parser, serve-static): status + expose */
function httpErrorsmi(
x: unknown,
): x is Error & { status: number; expose: boolean; type?: unknown } {
return x instanceof Error &&
"status" in x && typeof x.status === "number" &&
"expose" in x && typeof x.expose === "boolean";
}
export const tanaXatosi: ErrorRequestHandler = (
xato: unknown, _req, _res, next,
) => {
if (httpErrorsmi(xato) && xato.status < 500 && xato.expose) {
const tur = typeof xato.type === "string" ? xato.type : "";
next(new IlovaXatosi(
xato.status, TANA_XATOLARI[tur] ?? xato.message,
{ cause: xato },
));
return;
}
next(xato);
};TANA_XATOLARI va httpErrorsmi — 25-qismdagi xato-ishlovchi.ts dan aynan ko'chdi. { cause: xato } — asl xato yo'qolmasin, logda ko'rinsin (error.cause).
8.5 main.ts va ishga-tushir.ts
main.ts — 25–27 dagi server.ts ning o'rni. Mazmuni deyarli o'sha:
// main.ts — kirish nuqtasi (25–27 server.ts ning o'rni): config
// (fail-fast) → ombor → Nest ilovasi → server → jarayon hodisalari.
// Ishga tushirish: npm run build && npm start
// (node --env-file-if-exists=.env dist/main.js)
import "reflect-metadata";
import { configYukla, ConfigXatosi } from "./config.ts";
import { ishgaTushir, omborYarat } from "./ishga-tushir.ts";
import { jarayonniBoshqar } from "./jarayon.ts";
import type { Config } from "./config.ts";
import { joriyLog, logYarat } from "./umumiy/log.ts";
import { webhookXabarchi } from "./umumiy/webhook.ts";
let config: Config;
try {
config = configYukla(process.env);
} catch (x) {
if (!(x instanceof ConfigXatosi)) throw x;
console.error(x.message);
// EX_CONFIG (sysexits.h) — supervisor qayta-qayta ishga
// tushirmasin, sozlamani tuzatish kerak
process.exit(78);
}
const log = logYarat(config.LOG_LEVEL);
const saqlash = await omborYarat(config, log);
const { server, manzil, app } = await ishgaTushir({
port: config.PORT,
host: config.HOST,
bogliqliklar: {
ombor: saqlash.ombor,
corsOriginlar: config.CORS_ORIGINS,
tz: config.APP_TZ,
trustProxy: config.TRUST_PROXY,
hsts: config.HSTS,
log,
nestLogi: ["error", "warn"],
...(config.WEBHOOK_URL === undefined ? {} : {
xabarchi: webhookXabarchi(
config.WEBHOOK_URL, { log: () => joriyLog(log) },
),
}),
},
});
log.info(
{ manzil, muhit: config.NODE_ENV, saqlash: config.SAQLASH },
`vazifalar-api: ${manzil}`,
);
jarayonniBoshqar(server, {
info: (x) => { log.info(x); },
// fatal sinxron yoziladi (pino stdout'ga sinxron) — exit'dan oldin
// yo'qolmaydi
fatal: (x, xato) => { log.fatal({ err: xato }, x); },
}, {
// server allaqachon yopilgan (yop) — app.close() Nest modullarining
// yopish hook'larini chaqiradi, keyin pool/redis
oxirida: async () => {
await app.close();
await saqlash.yop();
},
});Uch farq: birinchi qator import "reflect-metadata" (Nest metama'lumoti uchun), nestLogi va yopishda app.close(). ishgaTushir endi app ni ham qaytaradi.
ishga-tushir.ts da omborYarat o'zgarmaydi. O'zgaradigani — ishgaTushir funksiyasi:
type Sozlama = {
port?: number;
host?: string;
bogliqliklar?: Bogliqliklar;
};
export async function ishgaTushir({
port = 0,
host = "127.0.0.1",
bogliqliklar = { ombor: new XotiraOmbori() },
}: Sozlama = {}): Promise<Ishlayotgan> {
const app = await ilovaYarat(bogliqliklar);
// Nest http serverni NestFactory.create() da o'zi yaratadi —
// timeout'larni listen() dan OLDIN shu obyektga beramiz
const server = app.getHttpServer();
// Load balancer (nginx, ALB) bo'sh ulanishni ~60 s ushlaydi — Node
// undan KEYIN yopsin
server.keepAliveTimeout = 65_000;
// Slowloris: sarlavhalar 10 s ichida to'liq kelmasa — 408
server.headersTimeout = 10_000;
server.requestTimeout = 30_000;
await app.listen(port, host); // EADDRINUSE — rad etilgan Promise
const { port: haqiqiy } = server.address() as AddressInfo;
return { server, manzil: `http://${host}:${String(haqiqiy)}`, app };
}25-qismda createServer({ keepAliveTimeout, … }, ilova) o'zimiz yaratardik. Nest serverni create da o'zi yaratadi — timeout'lar o'sha obyektga listen dan oldin yoziladi (Server timeout'lari va keep-alive). Ishlayotgan turiga app: NestExpressApplication maydoni qo'shiladi; faylning boshidagi createServer importi ketadi, NestExpressApplication turi keladi.
8.6 Eski fayllar va testlar
Endi npm run tip ni yurgizsak (biz kanon nusxasida yurgizdik):
src/vazifalar/marshrut.ts(6,10): error TS2305: Module '"../umumiy/json-kerak.ts"' has no exported member 'jsonKerak'.
test/mock.test.ts(20,17): error TS2345: Argument of type 'Promise<NestExpressApplication<Server<typeof IncomingMessage, typeof ServerResponse>>>' is not assignable to parameter of type 'App'.
test/supertest.test.ts(14,31): error TS2345: Argument of type 'Promise<NestExpressApplication<Server<typeof IncomingMessage, typeof ServerResponse>>>' is not assignable to parameter of type 'App'.(Jami 18 ta: bitta TS2305 va 17 ta TS2345 — mock.test.ts da 9, supertest.test.ts da 8; qolganlari bir xil.)
Tarjimasi: TS2305 — "json-kerak.ts modulida jsonKerak nomli eksport yo'q". json-kerak.ts da endi faqat jsonTanaKerak qoldi (eski middleware olib tashlandi). Uni so'rayotgan yagona fayl — eski Express routeri marshrut.ts. TS2345 — "Promise<NestExpressApplication> turidagi argument App turiga mos emas". Testlar request(ilovaYarat(...)) qilardi, endi ilovaYarat Promise qaytaradi.
Birinchi xato — "eski fayllarni o'chiring" signali:
git rm src/server.ts src/vazifalar/marshrut.ts \
src/vazifalar/xizmat.ts src/eksport/eksport.ts \
src/umumiy/xato-ishlovchi.ts \
src/salom.controller.ts src/salom.controller.spec.tsIkkinchisi uchun test/yordamchi.ts ga kichik yordamchi:
/** supertest uchun: Nest ilovasi (init qilingan, listen qilinmagan)
* http serveri — request(server) o'zi vaqtinchalik tinglatadi */
export async function httpIlova(b: Bogliqliklar): Promise<Server> {
return (await ilovaYarat(b)).getHttpServer();
}Testlarda ilovaYarat({...}) → await httpIlova({...}). Qolgan o'zgarishlar mexanik — node:test dan Vitest'ga:
node:test (25–27) |
Vitest |
|---|---|
import { test } from "node:test" |
import { test } from "vitest" |
before, after |
beforeAll, afterAll |
test(nom, { skip: "…" }, fn) |
test.skip(nom, fn) |
t.mock.fn(...) |
vi.fn(...) |
t.mock.method(obyekt, "m") |
vi.spyOn(obyekt, "m") |
mock.callCount() |
mock.calls.length |
t.mock.timers.enable({ apis: ["Date"] }) |
vi.useFakeTimers({ toFake: ["Date"] }) |
node:assert/strict qoladi — Vitest ichida ham ishlaydi. Test tanalari (nimani tekshirishi) o'zgarmaydi. Kanon bu almashtirishni kichik skript bilan qildi; siz qo'lda yoki muharriringizning "qidir va almashtir"i bilan qilasiz.
Uch joyda qo'shimcha ish bor:
jarayon.testbola jarayondanode qolip/halokat.tsni yurgizardi. Endi u Nest kodini import qiladi — type stripping@da yiqiladi (Nest CLI darsidagi xato). Yangitest/qolip/yurgiz.mjsfaylni Vite orqali ishga tushiradi (Vitest bilan bir xil o'girish):jsimport { runnerImport } from "vite"; await runnerImport(process.argv[2], { logLevel: "silent" });Vitest'ning sukut test vaqti — 5 s (
node:testda chegara yo'q edi). Bola jarayon ~2 s da ko'tariladi, shuning uchun testga{ timeout: 20_000 }beriladi.mock.test—t.mocktestdan keyin o'zi tiklanardi. Vitest'daafterEach(() => { vi.restoreAllMocks(); vi.useRealTimers(); })kerak.migratsiya.test— 2-darsda va'da qilingan beqaror test. Kanon sababini topdi:afterAlldapool.end()mijoz soketi yopilishini kutmaydi, darhol keyingiDROP DATABASE … WITH (FORCE)o'sha seansni o'ldiradi. MijozFATAL 57P01oladi, uni tinglovchi yo'q — ushlanmagan xato. Vitest buniUnhandled Errorsdeb ko'rsatadi va exit 1. Bu xato 27-qismda ham bor edi,node:testuni kamdan-kam ko'rsatardi. Tuzatish —FORCEsiz o'chirish, seans hali bo'lsa (55006) qisqa kutib qayta urinish:tsfor (let i = 0; ; i++) { try { await admin.query( "DROP DATABASE IF EXISTS vazifalar_test_mig", ); break; } catch (x) { const kod = (x as { code?: string }).code; if (i >= 40 || kod !== "55006") throw x; await new Promise((r) => setTimeout(r, 50)); } }Kanon tuzatishdan keyin 6 marta ketma-ket yurgizdi — 111/111.
Konfiguratsiya: vitest.config.ts da include: ["test/**/*.test.ts", "src/**/*.spec.ts"] (25–27 fayl nomlari saqlandi); package.json dan start:express va test:eski skriptlari olib tashlanadi, check — npm run tip && npm run lint && npm test. eslint.config.js dagi no-floating-promises izohi yangilanadi: Vitest'ning test() i void qaytaradi, qoida esa qoladi.
8.7 Tekshiruv
npm run check
npm run build
SAQLASH=xotira npm startKanon tekshiruvi (Node 24.21.0, NestJS 12.2.0, Vitest 5.0.5, PostgreSQL 18.6):
--- npm run tip
exit 0 (4485 ms)
--- npm run lint
exit 0 (9198 ms)
--- npm run test
↓ test/konteyner.test.ts (1 test | 1 skipped)
Test Files 20 passed | 1 skipped (21)
Tests 111 passed | 1 skipped (112)
exit 0 (7300 ms)
--- npm run build
exit 0 (5391 ms)25–27 ning 112 testi Nest ilovasida o'tdi: shartnoma, supertest, xatolar, idempotentlik, CORS, ETag/304, If-Match, eskirgan yo'l, eksport, log korrelyatsiyasi. Bitta SKIP — Testcontainers (TESTCONTAINERS=1 bilan alohida, 10/10). Vitest 7.3 s; 27-qismdagi node:test — 18.7 s.
Biz ham kanon nusxasida, bazasiz yurgizdik: tip 0, lint 0, Vitest 71 o'tdi + 5 fayl SKIP (Postgres va Redis kerak). Birinchi yugurishda jarayon.test 20 s ga yetib yiqildi (sovuq Vite yuklanishi, mashina band edi), keyingi ikki yugurishda o'tdi. Sizda ham shunday bo'lsa — qayta yurgizing; doim yiqilsa, xabarini o'qing.
Jonli so'rovlar (biz ishga tushirdik, SAQLASH=xotira; helmet sarlavhalari, Date, ETag, X-Request-Id olib tashlandi):
$ curl -X POST -H 'Content-Type: application/json' -d '{"matn":"Non olish #bozor"}' /v1/vazifalar
HTTP/1.1 201 Created
Location: /v1/vazifalar/1
{"id":1,"matn":"Non olish #bozor","bajarildi":false,"teglar":["bozor"],…,"versiya":1}
$ curl /vazifalar/1
HTTP/1.1 200 OK
Deprecation: @1792800000
Sunset: Sun, 24 Jan 2027 00:00:00 GMT
Link: </v1/vazifalar/1>; rel="successor-version"
$ curl -X POST -H 'Content-Type: application/json' -d '{"format":"csv"}' /v1/eksportlar
HTTP/1.1 202 Accepted
Location: /v1/eksportlar/ca52023c-1aca-4836-8f63-94929713096c
Retry-After: 1
$ curl /v1/teglar
HTTP/1.1 200 OK
{"teglar":[{"teg":"bozor","soni":1}]}Eksport ID — randomUUID(), sizda boshqa chiqadi. Kanon xuddi shu ko'chishni 9 so'rov bilan uch saqlash rejimida (Postgres, Prisma, Prisma + kesh) tekshirgan: Express versiyasidan farq faqat port va vaqt qatorlarida.
8.8 Commit
git add -A
M="feat!: vazifalar-api Nest'ga ko'chdi — modullar,"
M+=" controller'lar, xizmat; testlar Vitest'da,"
M+=" HTTP xulqi o'zgarmadi"
git commit -m "$M"feat! — undov belgisi "buzuvchi o'zgarish": tashqi API o'zgarmadi, lekin ishga tushirish usuli o'zgardi (endi build kerak, start:express yo'q). Prod bog'liqliklar soni: 27-qismda 10, endi 15.
9. Real ishda
main.tsning hajmi — loyihaning sog'lig'i belgisi. Odatda 20–60 qator:create, global sozlamalar,listen. Unda biznes mantiq bo'lmaydi.setGlobalPrefix("api")— frontend va API bitta domenda bo'lganda keng tarqalgan. Salomatlik yo'li uchunexcludedeyarli doim yoziladi.rawBody: true— Stripe, Payme kabi webhook qabul qiladigan loyihalarda majburiy sozlama.- Ko'chishda (Express → Nest) eng ko'p vaqt middleware tartibi va xato formatiga ketadi. Shartnoma testlari bo'lmasa, farqlarni mijozlar topadi.
- Intervyu: "
NestFactory.createnima qiladi?", "Global prefiks va versiyalash farqi?", "Nest'da raw body qanday olinadi?", "Nima uchunlistenda host muhim?".
Xulosa
NestFactory.createmodul daraxtini yig'adi, nusxalarni yaratadi va marshrutlarni ulaydi;listenportni ochadi.- Yaratish sozlamalari (
bodyParser,rawBody,bufferLogs,logger) —createda; global sozlamalar (setGlobalPrefix,enableCors,enableVersioning,useGlobalFilters) —createvalistenorasida. - Global prefiks Nest'ning 404 ishlovchisini ham prefiks ostiga qo'yadi — tashqaridagi yo'llar Express HTML'ini oladi.
- Nest Express xatolarini filtrdan oldin xaritalaydi:
typeyo'qoladi, katta tana 500 bo'lib qoladi — tarjima middleware'i bilan Nest'gacha ushlang. vazifalar-apito'liq Nest'da: Express routerlari o'chdi, 112 test Vitest'da o'tdi, HTTP javoblari o'zgarmadi.
Keyingi dars: Request lifecycle — so'rov middleware, guard, interceptor, pipe va filtrdan qaysi tartibda o'tishini kanon tajribasining haqiqiy izi bilan ko'ramiz.
Manbalar
- NestJS hujjati — First steps (
NestFactory,listen): https://docs.nestjs.com/first-steps - NestJS hujjati — Versioning: https://docs.nestjs.com/techniques/versioning
- NestJS hujjati — Raw body: https://docs.nestjs.com/faq/raw-body
- NestJS hujjati — Global prefix: https://docs.nestjs.com/faq/global-prefix
- NestJS hujjati — Logger (
bufferLogs,useLogger): https://docs.nestjs.com/techniques/logger - Kanon (IlmHamroh, 2026-10-27):
vazifalar-apibootstrap, tekshiruv (112 test, build),migratsiya.testbeqarorligi topilmasi va tuzatilishi. Bootstrap sozlamalari tajribasi,tanaXatosisiz va bilan javoblar, oraliqtipchiqishi, bazasiz Vitest va jonli so'rovlar — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!