Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. registerAs — nomlangan bo'lim
- 2.1 Tajriba
- 2.2 Uch qator — uch g'oya
- 2.3 Fabrika tekshirmaydi
- 3. validationSchema — Joi bilan
- 3.1 Sxema
- 3.2 Natijani o'qiymiz
- 4. validate — Zod bilan
- 4.1 Funksiya
- 4.2 ConfigService<Env, true> va infer: true
- 4.3 class-validator varianti
- 5. Uch yo'l yonma-yon va kanon tanlovi
- 6. Muhitlar
- 7. Fail-fast: xato ilovani qanday to'xtatadi
- 7.1 Zanjir
- 7.2 Nega 78?
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi yo'l?
- 2-mashq (o'rta): registerAs modeli
- 3-mashq (qiyin): hamma xatoni yig'ib, exit kodini tanlash
- 10. Loyiha qadami: config faqat DI ichida
- 10.1 ilova.ts — config DI'dan
- 10.2 ishga-tushir.ts — port va host ham config'dan
- 10.3 main.ts — sakkiz parametr o'rniga hech narsa
- 10.4 Testlar jim bo'lsin
- 10.5 Tekshiruv va fail-fast'ni o'z ko'zingiz bilan
- 11. Real ishda
- Xulosa
- Manbalar
NestJS config validatsiyasi: registerAs, ConfigType, Joi va Zod
Qisqacha: Nest'da config'ni ishga tushishda tekshirishning uch yo'li bor. Birinchisi —
validationSchema(Joi sxemasi). Ikkinchisi —validate(o'z funksiyangiz: Zod, class-validator). Uchinchisi —load+registerAs, ya'ni tipli obyekt qaytaradigan fabrika.registerAs("baza", () => ({ ... }))nomlangan bo'lim yaratadi: xizmat uni@Inject(baza.KEY)bilan,ConfigType<typeof baza>turida oladi —get("...")siz va satr emas, tayyor son bilan. Sozlama noto'g'ri bo'lsa, ilova umuman ko'tarilmaydi. Kanonimizda bu xatoexit 78bilan tugaydi.
Bu darsda
registerAsbilan nomlangan config bo'limi yasaysiz va uniConfigTypebilan tipli inject qilasiz.- Joi
validationSchemava Zodvalidateni haqiqiy xato xabarlari bilan solishtirasiz. ConfigService<Env, true>vainfer: truebilangetni tipli qilasiz.- Kanon nega
validateemas,registerAs+ 25-qismconfigYuklani tanlaganini tushuntirasiz. vazifalar-apidamain.tsconfig o'qishni to'xtatadi — sozlama to'liq DI ichiga o'tadi, xatoexit 78bilan tugaydi.
Oldin bilishingiz kerak: @nestjs/config, Env validatsiyasi va tipli config obyekti, Backend validatorlari: Joi, class-validator va express-validator, Process darajasidagi xatolar va crash strategiyasi.
O'tgan darsda ConfigModule ni ko'rdik va ikki zaif joyini topdik: get doim satr qaytaradi, kalit nomidagi xato esa jim undefined beradi. Bu darsda ikkalasini yopamiz.
1. Nega bu kerak?
25-qismda bir qoida qo'ygandik: noto'g'ri sozlama bilan ilova ishga tushmasin (fail-fast). PORT=abc bo'lsa — darhol xato va to'xtash. Birinchi so'rovda emas, ertalab soat uchda emas — deploy paytida, odam qarab turganida.
ConfigService.get bu qoidani buzadi. U hech narsani tekshirmaydi: PORT=abc ham "abc" bo'lib qaytadi. Xato keyin, listen("abc") da yoki undan ham keyin chiqadi.
O'xshatish: aeroportdagi ro'yxatdan o'tish. Pasport muddati o'tgan bo'lsa, sizni kirishdayoq to'xtatishadi — samolyot uchib ketgandan keyin emas. Config validatsiyasi — ilova eshigidagi shu nazorat.
2. registerAs — nomlangan bo'lim
2.1 Tajriba
registerAs(nom, fabrika) — config'ning bir bo'limini fabrika funksiya bilan e'lon qiladi. Biz demo loyihada sinadik (biz ishga tushirdik: NestJS 12.2.0, @nestjs/config 12.0.1):
const baza = registerAs("baza", () => ({
host: process.env.DB_HOST ?? "127.0.0.1",
port: Number(process.env.DB_PORT ?? 5432),
}));
@Injectable()
class BazaXizmati {
constructor(
@Inject(baza.KEY) private readonly b: ConfigType<typeof baza>,
private readonly config: ConfigService,
) {}
chiqar(): void {
console.log("baza.KEY:", String(baza.KEY));
console.log("b.port:", this.b.port, typeof this.b.port);
console.log("get('baza.host'):", this.config.get("baza.host"));
}
}
process.env.DB_PORT = "6432";
@Module({
imports: [
ConfigModule.forRoot({ ignoreEnvFile: true, load: [baza] }),
],
providers: [BazaXizmati],
})
class M1 {}Natija:
baza.KEY: CONFIGURATION(baza)
b.port: 6432 number
get('baza.host'): 127.0.0.12.2 Uch qator — uch g'oya
baza.KEY—registerAsfabrikaga avtomatik token qo'shadi:CONFIGURATION(baza)satri. Bu Custom provider'lar darsidagi token g'oyasining o'zi:@Inject(baza.KEY)— "shu tokendagi qiymatni ber".b.port: 6432 number— son! Fabrika ichidaNumber(...)bilan aylantirdik. Endi xizmat satr bilan ishlamaydi.ConfigType<typeof baza>— fabrikaning qaytish turi: TypeScriptb.portninumber,b.hostnistringdeb biladi.b.protdeb yozsangiz — tip xatosi.get('baza.host')— bo'limConfigServiceda ham bor, nuqta bilan:"baza.host". Bu eski uslub uchun ko'prik; tipli yo'l —@Inject(baza.KEY).
Fabrika qachon chaqiriladi? ConfigModule yuklanayotganda — ilova ishga tushishda, bir marta. Shuning uchun process.env.DB_PORT ni modul yaratilishidan oldin qo'ysak, fabrika 6432 ni ko'rdi.
Maslahat:
registerAs— sozlamalarni mavzu bo'yicha bo'lish uchun:baza,redis,pochta,tolov. Har xizmat faqat o'ziga kerak bo'limni so'raydi.PochtaXizmatiga baza paroli yetib bormaydi — eng kam huquq qoidasi config'da ham ishlaydi.
2.3 Fabrika tekshirmaydi
Bitta muhim farq. registerAs fabrikasi faqat siz yozganni qiladi. Number("abc") — NaN, xato emas. Fabrika ichida tekshirish yozmasangiz, b.port jim NaN bo'ladi. Tekshirish — keyingi ikki bo'limning ishi.
Tekshirib ko'ring:
registerAs("redis", () => ({ url: process.env.REDIS_URL })). MuhitdaREDIS_URLyo'q. Ilova ishga tushadimi?ConfigType<typeof redis>daurlning turi qanday?
Javob
Ishga tushadi — fabrika xato tashlamaydi, url shunchaki undefined. Turi — string | undefined (process.env ning har kaliti shunday). TypeScript sizni undefined ni tekshirishga majburlaydi, lekin ishga tushishdagi to'xtatish yo'q. Fail-fast kerak bo'lsa — fabrikada tekshiring yoki validate ishlating.
3. validationSchema — Joi bilan
3.1 Sxema
Nest hujjatidagi klassik yo'l — Joi. Joi'ni 25-qismda boshqa validatorlar qatorida ko'rgandik (Backend validatorlari):
import Joi from "joi";
const joiSxema = Joi.object({
NODE_ENV: Joi.string()
.valid("development", "production", "test")
.default("development"),
PORT: Joi.number().port().default(3000),
});
ConfigModule.forRoot({
ignoreEnvFile: true,
validationSchema: joiSxema,
});Ikki holatda sinadik (biz ishga tushirdik, Joi 18.3.1): PORT=8080, keyin PORT=abc va NODE_ENV=prod.
Joi OK: PORT = 8080 number | NODE_ENV = development
Joi XATO: Config validation error: NODE_ENV: "NODE_ENV" must be one of [development, production, test]
PORT: "PORT" must be a number3.2 Natijani o'qiymiz
PORT = 8080 number— Joi qiymatni aylantirdi vaConfigServicega son bo'lib tushdi.NODE_ENVberilmagan — sukutdevelopmentqo'yildi.- Ikkinchi holat — ilova yaratilmadi. Xabarda ikkala xato birdan:
NODE_ENVham,PORTham. Nest Joi'niabortEarly: falsebilan chaqiradi — birinchi xatoda to'xtamaydi. Bu qulay: deploy paytida hamma muammoni bir marta ko'rasiz.
Tarjima: "Config tekshiruvi xatosi: NODE_ENV — development, production, test dan biri bo'lishi kerak. PORT — son bo'lishi kerak."
Joi'ning kamchiligi: tur TypeScript'ga o'tmaydi. get("PORT") ning turi baribir any. Sxemani va TypeScript turini alohida yozish kerak — ikki joy, ikki haqiqat.
4. validate — Zod bilan
4.1 Funksiya
validate — istalgan funksiya: xom process.env ni oladi, tekshirilgan obyekt qaytaradi yoki xato tashlaydi. Zod bilan:
const EnvSxema = z.object({
PORT: z.coerce.number().int().default(3000),
SAQLASH: z.enum(["xotira", "postgres"]).default("xotira"),
});
type Env = z.infer<typeof EnvSxema>;
ConfigModule.forRoot({
ignoreEnvFile: true,
validate: (raw) => EnvSxema.parse(raw),
});Natija (PORT=8080, keyin SAQLASH=mongo; xato xabarining boshi):
Zod OK: PORT = 8080 number | SAQLASH = xotira
Zod XATO: [
{
"code": "invalid_value",
"values": [
"xotira",
"postgres"
],
"path": [
"SAQLASH"
],
"message": "Invalid option: expected one of \"xotira\"|\"postgres\""
}Zod ham aylantirdi (z.coerce.number()) va sukutni qo'ydi. Xato xabari esa JSON ko'rinishida — ZodError ning xom message i. Odam uchun yomon. 25-qismda buning yechimini ko'rgandik: z.prettifyError (Zod 4 serverda). validate ichida parse o'rniga safeParse + prettifyError bilan o'z xatoingizni tashlang.
Diqqat:
z.coerce.number()bo'sh satrni0qiladi:PORT=(qiymatsiz) — jim0, ya'ni tasodifiy port. KanonimizdagiButun()sxemasi shuning uchuncoerceemas,regex(/^\d+$/)+Numberishlatadi — 25-qismda topilgan teshik (config.tsizohida yozilgan).
4.2 ConfigService<Env, true> va infer: true
validate bilan get ni tipli qilsa bo'ladi:
const c = app.get<ConfigService<Env, true>>(ConfigService);
const port = c.get("PORT", { infer: true }); // turi: number
c.get("PROT", { infer: true }); // tip xatosiConfigService<Env, true> — birinchi parametr: kalitlar va turlar xaritasi; ikkinchisi true — "qiymat tekshirilgan, undefined bo'lmaydi". { infer: true } — turni Env dan olish. Tajribada port ning ish vaqtidagi turi ham number edi. Bu ishlaydi, lekin har get da { infer: true } va har constructor'da uzun generic — shovqin ko'p.
4.3 class-validator varianti
Nest hujjatida yana bir yo'l bor: validate ichida class-validator. Klass yoziladi (@IsInt() PORT: number), plainToInstance bilan aylantiriladi, validateSync bilan tekshiriladi. Bu dekoratorlar bilan DTO va class-validator darsida tanishgansiz — naqsh o'sha. Biz bu variantni ishga tushirmadik (hujjat bo'yicha, 2026-11). Kanon class-validator'ni umuman ishlatmaydi — sababi Zod va Standard Schema NestJS'da darsida o'lchov bilan berilgan.
5. Uch yo'l yonma-yon va kanon tanlovi
| Turni beradi | Aylantiradi | Qaytaradi | |
|---|---|---|---|
validationSchema (Joi) |
yo'q | ha | kalitlar ConfigService da |
validate (Zod) |
infer bilan |
ha | kalitlar ConfigService da |
load + registerAs |
ha, ConfigType |
fabrika ichida | tayyor obyekt, token bilan |
Kanon uchinchisini oladi, lekin fabrika ichida 25-qismdagi configYukla ni chaqiradi:
const ilova = registerAs(
"ilova",
(): Config => config ?? configYukla(process.env),
);Nega validate: configYukla emas? Uch sabab:
- Bitta obyekt, bitta token. Xizmatlar
@Inject(CONFIG)bilan tayyor, muzlatilganConfigoladi — Bootstrap: main.ts darsidan beri shunday. Ularning kodi bir qator ham o'zgarmadi.validateesa natijaniConfigServiceichiga kalit-kalit qo'yadi va har joydaget(..., { infer: true })talab qiladi. configYuklaallaqachon sinalgan. 25-qismdagiconfig.test.ts— CORS origin normallash,productiondaCORS_ORIGINSmajburiyligi, bo'shPORT=teshigi. Bu testlar o'zgarmasdan o'tadi.- Test qulayligi. Test
ilovaYarat({ config: configYukla({ SAQLASH: "xotira" }) })bilan tayyor config beradi —process.envga tegmaydi.
Nima yo'qotdik: Nest hujjatidagi "standart" ko'rinish. Nest loyihasiga yangi kelgan dasturchi validationSchema ni qidiradi. Kanon buning o'rniga bogliqliklar.module.ts boshidagi izohga tayanadi.
6. Muhitlar
Bir ilova uch muhitda ishlaydi: lokal (development), test va production. Ularda qoidalar ham farq qiladi. Kanonda bu ikki joyda:
- Sxema ichida.
config.tsdagirefine:productiondaCORS_ORIGINSbo'sh bo'lsa — xato. Lokalda bo'sh ro'yxat ruxsat. - Fayllar. Nest uslubida
envFilePath: [.env.${process.env.NODE_ENV}, ".env"]keng tarqalgan. Kanonda fayl umuman Nest'ga o'qitilmaydi (ignoreEnvFile), muhit faylininpmskripti beradi:node --env-file-if-exists=.env. Testda esavitest.config.tsdagienv— keyingi «Loyiha qadami» bo'limida.
7. Fail-fast: xato ilovani qanday to'xtatadi
7.1 Zanjir
configYukla xato tashlaganda nima bo'ladi? Fabrika Nest ichida, NestFactory.create paytida chaqiriladi. Xato u yerdan main.ts gacha shunday yetadi:
sequenceDiagram
participant M as main.ts
participant N as NestFactory
participant R as registerAs fabrikasi
participant Z as configYukla (Zod)
M->>N: create(AppModule)
N->>R: ConfigModule yuklanmoqda
R->>Z: configYukla(process.env)
Z-->>R: ConfigXatosi
R-->>N: xato
N-->>M: rad etilgan Promise
M->>M: exit 78Diagrammaning o'rtasi muhim: NestFactory xatoni yutib yubormaydi va jarayonni o'zi o'ldirmaydi. Buning uchun ilova.ts da abortOnError: false bor. Usiz Nest provider xatosida process.exit(1) ni o'zi chaqiradi — main.ts dagi catch ga navbat kelmaydi va 78 kodini qo'yib bo'lmaydi.
7.2 Nega 78?
Exit kodlari process obyekti darsidan tanish: 0 — muvaffaqiyat, 1 — umumiy xato. 78 — Unix'ning sysexits.h dagi EX_CONFIG: "sozlama xatosi". Ma'nosi nazoratchi (supervisor) uchun: qayta-qayta ishga tushirish foyda bermaydi, odam sozlamani tuzatishi kerak (crash strategiyasi). Bazaga ulanib bo'lmasa — bu boshqa xato: exit 1, qayta urinish mantiqli.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
b.port — NaN |
registerAs fabrikasi tekshirmaydi |
fabrikada tekshiring yoki Zod |
PORT= bilan tasodifiy port |
z.coerce.number("") → 0 |
regex(/^\d+$/) + Number |
get("PORT") baribir any |
Joi turni bermaydi | ConfigType yoki ConfigService<Env, true> |
| Xato xabari — JSON uyumi | parse ning xom ZodError i |
safeParse + z.prettifyError |
exit 1, 78 emas |
abortOnError sukutda true |
NestFactory.create(..., { abortOnError: false }) |
Nest can't resolve … CONFIGURATION(baza) |
bo'lim load ga qo'shilmagan |
load: [baza] yoki ConfigModule.forFeature(baza) |
ConfigModule.forFeature(baza) — bo'limni faqat bitta feature modulda ro'yxatga qo'yish. Hujjatda bor; biz uni sinamadik.
9. Mashqlar
Mashqlar kurs/mashqlar/30/28-config-validatsiyasi/ da.
1-mashq (oson): qaysi yo'l?
To'ldiring:
- Xato xabarida hamma muammo birdan chiqadi, chunki Nest Joi'ni
abortEarly:bilan chaqiradi. registerAs("pochta", ...)tokeni satr ko'rinishida:@Inject(pochta.KEY)qiymatining turi:ConfigType<typeof>- Config xatosida kanon qaytaradigan exit kodi:
Yechim
- false — birinchi xatoda to'xtamaslik uchun.
- CONFIGURATION(pochta) — tajribadagi
CONFIGURATION(baza)kabi. - pochta —
ConfigTypefabrikaning qaytish turini oladi. - 78 —
EX_CONFIG; boshqa ishga tushish xatolari — 1.
2-mashq (o'rta): registerAs modeli
Brauzerda ishlaydigan model yozing. registerAs(nom, fabrika) fabrikani qaytarsin, unga KEY = "CONFIGURATION(nom)" xususiyati qo'shilsin. configYukla(bolimlar) har fabrikani bir marta chaqirib, { [nom]: natija } obyekt yasasin. get(store, "baza.port") nuqtali yo'l bo'yicha qiymat qaytarsin. Ishora: Object.assign(fabrika, { KEY }), yol.split(".").reduce(...) (reduce).
Yechim
function registerAs(nom, fabrika) {
return Object.assign(fabrika, {
KEY: `CONFIGURATION(${nom})`,
nom,
});
}
function configYukla(bolimlar) {
const store = {};
for (const b of bolimlar) store[b.nom] = b();
return store;
}
function get(store, yol) {
return yol.split(".").reduce((q, k) => q?.[k], store);
}
const env = { DB_PORT: "6432" };
const baza = registerAs("baza", () => ({
host: env.DB_HOST ?? "127.0.0.1",
port: Number(env.DB_PORT ?? 5432),
}));
const pochta = registerAs("pochta", () => ({
kimdan: "salom@example.com",
}));
const store = configYukla([baza, pochta]);
console.log(baza.KEY);
console.log(get(store, "baza.port"), typeof get(store, "baza.port"));
console.log(get(store, "pochta.kimdan"));
console.log(get(store, "baza.parol"));Konsolda:
CONFIGURATION(baza)
6432 number
salom@example.com
undefinedModel Nest natijasining uchala qatorini takrorladi. Soddalashgan joyi: haqiqiy ConfigModule har bo'limni alohida provider qiladi va KEY — DI tokeni. Bizda KEY — oddiy satr. Oxirgi qator — get ning eski muammosi: yo'q kalit jim undefined.
3-mashq (qiyin): hamma xatoni yig'ib, exit kodini tanlash
tekshir(env, qoidalar) yozing. qoidalar — { KALIT: funksiya }; funksiya xom satrni olib, { ok: true, qiymat } yoki { ok: false, xabar } qaytaradi. tekshir birinchi xatoda to'xtamasin: hamma xabarni yig'sin. Natija: xato bo'lmasa — { kod: 0, config }, bo'lsa — { kod: 78, xatolar }. Ikki qoida yozing: PORT (faqat raqamlar, sukut "3000", 65535 dan oshmasin) va SAQLASH (xotira yoki postgres, sukut xotira). Ishora: /^\d+$/.test(x) (RegExp).
Yechim
const butun = (sukut, maks) => (x = sukut) => {
if (!/^\d+$/.test(x)) {
return { ok: false, xabar: "butun son bo'lishi kerak" };
}
const n = Number(x);
return n > maks
? { ok: false, xabar: `${maks} dan oshmasin` }
: { ok: true, qiymat: n };
};
const biri = (sukut, ruxsat) => (x = sukut) =>
ruxsat.includes(x)
? { ok: true, qiymat: x }
: { ok: false, xabar: `${ruxsat.join(" | ")} dan biri` };
const qoidalar = {
PORT: butun("3000", 65_535),
SAQLASH: biri("xotira", ["xotira", "postgres"]),
};
function tekshir(env, qoidalar) {
const config = {};
const xatolar = [];
for (const [kalit, qoida] of Object.entries(qoidalar)) {
const n = qoida(env[kalit]);
if (n.ok) config[kalit] = n.qiymat;
else xatolar.push(`✖ ${n.xabar}\n → at ${kalit}`);
}
return xatolar.length === 0
? { kod: 0, config: Object.freeze(config) }
: { kod: 78, xatolar };
}
console.log(tekshir({}, qoidalar));
const yomon = tekshir({ PORT: "abc", SAQLASH: "mongo" }, qoidalar);
console.log(yomon.kod);
console.log(yomon.xatolar.join("\n"));
console.log(tekshir({ PORT: "70000" }, qoidalar).xatolar[0]);Konsolda:
{ kod: 0, config: { PORT: 3000, SAQLASH: 'xotira' } }
78
✖ butun son bo'lishi kerak
→ at PORT
✖ xotira | postgres dan biri
→ at SAQLASH
✖ 65535 dan oshmasin
→ at PORTXabar shakli — kanondagi z.prettifyError chiqishiga o'xshash (✖ … va → at PORT). x = sukut — sukut parametr: env[kalit] undefined bo'lsa ishlaydi, bo'sh satr "" bo'lsa — yo'q, va /^\d+$/ uni rad etadi. PORT= teshigi shu bilan yopiladi.
10. Loyiha qadami: config faqat DI ichida
O'tgan darsda ConfigModule BogliqliklarModule ichiga kirdi. Lekin main.ts hali config'ni o'zi o'qib, sakkiz qiymatni qo'lda uzatyapti. Endi buni olib tashlaymiz: ilova sozlamalarni DI'dagi CONFIG dan oladi. Commit — nestjs-cls darsida.
10.1 ilova.ts — config DI'dan
ilovaYarat parametrlarining sukutlari (corsOriginlar = [], tz = "Asia/Tashkent", log = logYarat("silent") …) o'chadi. Parametr berilsa — u, berilmasa — DI'dagi Config dan. Faylning yangi matni (formatlangan, izohlar qisqartirildi; nestLogi — vaqtincha, keyingi darsda o'chadi):
// 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 — supertest).
// Sozlamalar DI'dagi Config'dan (ConfigModule) — parametrlar faqat
// testlarda ustidan yozish uchun
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 { XotiraIdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import { JSON_TURLARI } from "./umumiy/json-kerak.ts";
import { tanaXatosi } from "./umumiy/tana-xatosi.ts";
import { 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";
import type { Config } from "./config.ts";
import { CONFIG, LOG } from "./tokenlar.ts";Turdan keyingi qism:
export async function ilovaYarat({
ombor, config, corsOriginlar,
idempotentlikOmbori = new XotiraIdempotentlikOmbori(),
log, xabarchi, tz, trustProxy, hsts, nestLogi = false,
}: Bogliqliklar): Promise<NestExpressApplication> {
// bodyParser: false — Nest o'z JSON parser'ini qo'ymasin.
// abortOnError: false — fabrika yiqilsa (config xato, baza yo'q)
// Nest process.exit(1) QILMASIN, xato Promise orqali chiqsin.
const app = await NestFactory.create<NestExpressApplication>(
AppModule.forRoot({
ombor, config, log, xabarchi, tz, idempotentlikOmbori,
}),
{ bodyParser: false, logger: nestLogi, abortOnError: false },
);
const c = app.get<Config>(CONFIG);
const l = app.get<Log>(LOG);
app.set("trust proxy", trustProxy ?? c.TRUST_PROXY);
app.disable("x-powered-by"); // "Express" ekanini oshkor qilmaymiz
// app.use() — Express middleware'lari, Nest marshrutlaridan OLDIN
app.use(sorovId());
// so'rov logi + AsyncLocalStorage konteksti (sorovId)
app.use(sorovLogi(l));
app.use(helmet({ strictTransportSecurity: hsts ?? c.HSTS }));
app.use(
cors({
origin: [...(corsOriginlar ?? c.CORS_ORIGINS)],cors ning qolgan qismi, statik fayllar, express.json va versiyalash o'zgarmaydi. Bogliqliklar turida parametr izohlari bitta umumiy izohga qisqardi: "berilsa — config'dagi qiymat o'rniga (testlar)".
Asosiy qator — app.get<Config>(CONFIG). app.get — ilova konteyneridan provider olish (DI va IoC konteyner). NestFactory.create tugaganda config allaqachon tekshirilgan: xato bo'lsa, bu qatorga yetib kelmasdik. ?? — test bergan qiymat ustun, bo'lmasa config.
10.2 ishga-tushir.ts — port va host ham config'dan
// ishga-tushir.ts — Nest ilovasi, server timeout'lari, tinglash
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
import type {
NestExpressApplication,
} from "@nestjs/platform-express";
import { ilovaYarat } from "./ilova.ts";
import type { Bogliqliklar } from "./ilova.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
import { CONFIG } from "./tokenlar.ts";
import type { Config } from "./config.ts";
// 27 testlari omborYarat'ni shu fayldan oladi — yo'l saqlanadi
export { omborYarat } from "./ombor-yarat.ts";
export type { OmborLogi, Saqlash } from "./ombor-yarat.ts";
export type Ishlayotgan = {
server: Server; manzil: string; app: NestExpressApplication;
};
type Sozlama = {
port?: number; host?: string; bogliqliklar?: Bogliqliklar;
};
/** port/host berilmasa — config'dan (PORT, HOST); testlar port 0 */
export async function ishgaTushir({
port, host, bogliqliklar = { ombor: new XotiraOmbori() },
}: Sozlama = {}): Promise<Ishlayotgan> {
const app = await ilovaYarat(bogliqliklar);
const c = app.get<Config>(CONFIG);
const p = port ?? c.PORT;
const h = host ?? c.HOST;
// timeout'lar listen() dan OLDIN Nest yaratgan http serverga
const server = app.getHttpServer();
// Load balancer bo'sh ulanishni ~60 s ushlaydi — Node undan KEYIN
server.keepAliveTimeout = 65_000;
// Slowloris: sarlavhalar 10 s ichida kelmasa — 408
server.headersTimeout = 10_000;
server.requestTimeout = 30_000;
await app.listen(p, h); // EADDRINUSE — rad etilgan Promise
const { port: haqiqiy } = server.address() as AddressInfo;
return { server, manzil: `http://${h}:${String(haqiqiy)}`, app };
}10.3 main.ts — sakkiz parametr o'rniga hech narsa
// main.ts — kirish nuqtasi: Nest ilovasi (config — ConfigModule,
// ombor — OmborModule, DI ichida) → server → jarayon hodisalari.
// Ishga tushirish: npm run build && npm start
import "reflect-metadata";
import { ConfigXatosi } from "./config.ts";
import type { Config } from "./config.ts";
import { ishgaTushir } from "./ishga-tushir.ts";
import type { Ishlayotgan } from "./ishga-tushir.ts";
import { jarayonniBoshqar } from "./jarayon.ts";
import { CONFIG, LOG } from "./tokenlar.ts";
import type { Log } from "./umumiy/log.ts";
let ishlayotgan: Ishlayotgan;
try {
// sozlama, log, ombor — hammasi DI ichida (ConfigModule →
// configYukla: Zod, fail-fast)
ishlayotgan = await ishgaTushir({
bogliqliklar: { nestLogi: ["error", "warn"] },
});
} catch (x) {
// boshqa xato (baza yo'q, EGASI_ID…) — stack bilan, exit 1
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 { server, manzil, app } = ishlayotgan;
const config = app.get<Config>(CONFIG);
const log = app.get<Log>(LOG);
log.info(
{ manzil, muhit: config.NODE_ENV, saqlash: config.SAQLASH },
`vazifalar-api: ${manzil}`,
);
// Signal va "let it crash" — o'zimizniki (jarayon.ts): avval HTTP
// ulanishlar yopiladi, keyin app.close() — lifecycle hook'lari
jarayonniBoshqar(server, {
info: (x) => { log.info(x); },
// fatal sinxron yoziladi — exit'dan oldin yo'qolmaydi
fatal: (x, xato) => { log.fatal({ err: xato }, x); },
}, { oxirida: () => app.close() });Avval configYukla, logYarat, webhookXabarchi importlari bor edi — endi yo'q. main.ts faqat ishga tushiradi va jarayon hodisalarini ulaydi. Config, log va ombor qayerdan kelishini DI biladi. Top-level await — ESM'da ruxsat (ESM Node.js'da).
10.4 Testlar jim bo'lsin
Endi log berilmasa, LOG provider'i config.LOG_LEVEL bo'yicha logger yaratadi — sukut info. Testlar terminalni log bilan to'ldirib yuboradi. vitest.config.ts ga test muhiti uchun o'zgaruvchi qo'shiladi:
include: ["test/**/*.test.ts", "src/**/*.spec.ts"],
// config process.env dan — testlar jim bo'lsin (o'z logini
// bergan testlar — xotiraLogi — bundan mustasno)
env: { LOG_LEVEL: "silent" },test/ombor-modul.test.ts dagi EGASI_ID=999999 testi o'z config ini beradi — unga ham LOG_LEVEL: "silent" qo'shiladi: configYukla({ SAQLASH: "postgres", DATABASE_URL: URL, EGASI_ID: "999999", LOG_LEVEL: "silent" }).
10.5 Tekshiruv va fail-fast'ni o'z ko'zingiz bilan
npm run tip && npm run lint && npm test && npm run build
PORT=abc node dist/main.js; echo "exit $?"Biz holat nusxasida yurgizdik (biz ishga tushirdik, Postgres'siz): tip 0, lint 0, Vitest — 75 passed | 6 skipped, build o'tdi. PORT=abc bilan:
[Nest] … ERROR [ExceptionHandler] ConfigXatosi: Sozlama noto'g'ri:
✖ butun son bo'lishi kerak (bo'sh qiymat — xato)
→ at PORT
at configYukla (file:///C:/Users/…/dist/config.js:74:15)
at InstanceWrapper.metatype (file:///C:/Users/…/dist/bogliqliklar.module.js:24:59)
at Injector.instantiateClass (file:///C:/Users/…/node_modules/@nestjs/core/injector/injector.js:527:55)
Sozlama noto'g'ri:
✖ butun son bo'lishi kerak (bo'sh qiymat — xato)
→ at PORT
exit 78Jarayon raqami, vaqt va lokal yo'llarni qisqartirdik; Nest injektorining yana yetti qatori olib tashlandi.
Chiqishda xato ikki marta. Birinchisi — Nest'ning ExceptionHandler logi: provider fabrikasi yiqilganini u ham yozadi (logger: ["error", "warn"] — error yoqilgan). Stack izida yo'l ko'rinadi: configYukla ← bogliqliklar.module.js (bizning registerAs fabrikasi) ← Injector. Ikkinchisi — main.ts dagi console.error(x.message): qisqa, odam uchun. Oxirida exit 78.
Kanon bazali muhitda ikkinchi holatni ham o'lchagan: EGASI_ID=999 (bazada yo'q foydalanuvchi) — xato ConfigXatosi emas, shuning uchun exit 1 (kanon o'lchovi, IlmHamroh, 2026-10). Sozlama to'g'ri, lekin ma'lumot yo'q — bu boshqa toifadagi muammo.
Hali commit qilmang — log va so'rov konteksti keyingi ikki darsda.
11. Real ishda
- Deploy xatolari ko'pincha config'dan: yangi o'zgaruvchi qo'shildi, serverga yozish unutildi. Fail-fast va aniq xabar (
→ at PORT) bunday deployni bir daqiqada to'xtatadi — eski versiya ishlab turaveradi. - Joi — eski Nest loyihalarida eng ko'p uchraydi; yangilarida Zod
validateko'paymoqda. Ikkalasini o'qiy olish kerak. registerAsbo'limlari katta loyihalarda modul chegarasi bilan mos:pochtabo'limiPochtaModuleda.- Intervyu: "Nest'da config qanday tekshiriladi?", "
ConfigTypenima beradi?", "ilova noto'g'ri sozlama bilan ishga tushsa nima bo'ladi?".
Xulosa
registerAs(nom, fabrika)— nomlangan bo'lim;@Inject(nom.KEY)+ConfigType<typeof nom>— tipli, aylantirilgan qiymat.- Fabrika o'zi tekshirmaydi —
Number("abc")jimNaN. validationSchema(Joi) — aylantiradi, hamma xatoni birdan beradi, lekin tur bermaydi.validate(Zod) — aylantiradi,ConfigService<Env, true>+infer: truebilan tipli; xabar uchunprettifyError.- Kanon:
registerAs("ilova", () => configYukla(process.env))— 25-qism sxemasi o'zgarmadi, xizmatlarCONFIGoladi. abortOnError: false+main.tsdagicatch→ config xatosidaexit 78, boshqa ishga tushish xatosidaexit 1.
Keyingi dars: NestJS Logger va nestjs-pino — Nest'ning o'z logger'ini ko'ramiz va uning tizim xabarlarini bizning pino JSON logimizga yo'naltiramiz.
Manbalar
- NestJS hujjati — Configuration: schema validation, custom validate, configuration namespaces,
ConfigType,infer: https://docs.nestjs.com/techniques/configuration - Joi API: https://joi.dev/api/
- Zod —
prettifyErrorvacoerce: https://zod.dev/error-formatting - FreeBSD
sysexits(3)—EX_CONFIG(78): https://man.freebsd.org/cgi/man.cgi?query=sysexits - Kanon (IlmHamroh, 2026-10-27): config/log/kontekst qadami —
registerAs("ilova")+configYukla,abortOnError: false,main.tsexit 78;EGASI_ID=999→ exit 1.registerAs, Joi 18.3.1 va Zodvalidatetajribalari, oraliq holat tekshiruvi vaPORT=abcchiqishi — 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!