IlmHamroh
JavaScript Full-stack/30-qism. NestJS28/65-dars19 daqiqa
Mundarija (32)

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 xato exit 78 bilan tugaydi.

Bu darsda

  • registerAs bilan nomlangan config bo'limi yasaysiz va uni ConfigType bilan tipli inject qilasiz.
  • Joi validationSchema va Zod validate ni haqiqiy xato xabarlari bilan solishtirasiz.
  • ConfigService<Env, true> va infer: true bilan get ni tipli qilasiz.
  • Kanon nega validate emas, registerAs + 25-qism configYukla ni tanlaganini tushuntirasiz.
  • vazifalar-api da main.ts config o'qishni to'xtatadi — sozlama to'liq DI ichiga o'tadi, xato exit 78 bilan 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):

ts
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:

text
baza.KEY: CONFIGURATION(baza)
b.port: 6432 number
get('baza.host'): 127.0.0.1

2.2 Uch qator — uch g'oya

  • baza.KEY — registerAs fabrikaga 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 ichida Number(...) bilan aylantirdik. Endi xizmat satr bilan ishlamaydi. ConfigType<typeof baza> — fabrikaning qaytish turi: TypeScript b.port ni number, b.host ni string deb biladi. b.prot deb yozsangiz — tip xatosi.
  • get('baza.host') — bo'lim ConfigService da 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. PochtaXizmati ga 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 })). Muhitda REDIS_URL yo'q. Ilova ishga tushadimi? ConfigType<typeof redis> da url ning 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):

ts
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.

text
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 number

3.2 Natijani o'qiymiz

  • PORT = 8080 number — Joi qiymatni aylantirdi va ConfigService ga son bo'lib tushdi. NODE_ENV berilmagan — sukut development qo'yildi.
  • Ikkinchi holat — ilova yaratilmadi. Xabarda ikkala xato birdan: NODE_ENV ham, PORT ham. Nest Joi'ni abortEarly: false bilan 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:

ts
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):

text
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 satrni 0 qiladi: PORT= (qiymatsiz) — jim 0, ya'ni tasodifiy port. Kanonimizdagi Butun() sxemasi shuning uchun coerce emas, regex(/^\d+$/) + Number ishlatadi — 25-qismda topilgan teshik (config.ts izohida yozilgan).

4.2 ConfigService<Env, true> va infer: true

validate bilan get ni tipli qilsa bo'ladi:

ts
const c = app.get<ConfigService<Env, true>>(ConfigService);
const port = c.get("PORT", { infer: true }); // turi: number
c.get("PROT", { infer: true }); // tip xatosi

ConfigService<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:

ts
const ilova = registerAs(
  "ilova",
  (): Config => config ?? configYukla(process.env),
);

Nega validate: configYukla emas? Uch sabab:

  1. Bitta obyekt, bitta token. Xizmatlar @Inject(CONFIG) bilan tayyor, muzlatilgan Config oladi — Bootstrap: main.ts darsidan beri shunday. Ularning kodi bir qator ham o'zgarmadi. validate esa natijani ConfigService ichiga kalit-kalit qo'yadi va har joyda get(..., { infer: true }) talab qiladi.
  2. configYukla allaqachon sinalgan. 25-qismdagi config.test.ts — CORS origin normallash, production da CORS_ORIGINS majburiyligi, bo'sh PORT= teshigi. Bu testlar o'zgarmasdan o'tadi.
  3. Test qulayligi. Test ilovaYarat({ config: configYukla({ SAQLASH: "xotira" }) }) bilan tayyor config beradi — process.env ga 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.ts dagi refine: production da CORS_ORIGINS bo'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 faylini npm skripti beradi: node --env-file-if-exists=.env. Testda esa vitest.config.ts dagi env — 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 78

Diagrammaning 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 — ConfigType fabrikaning 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
js
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:

text
CONFIGURATION(baza)
6432 number
salom@example.com
undefined

Model 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
js
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:

text
{ 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 PORT

Xabar 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):

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 — 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:

ts
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

ts
// 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

ts
// 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:

ts
    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

bash
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:

text
[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 78

Jarayon 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 validate ko'paymoqda. Ikkalasini o'qiy olish kerak.
  • registerAs bo'limlari katta loyihalarda modul chegarasi bilan mos: pochta bo'limi PochtaModule da.
  • Intervyu: "Nest'da config qanday tekshiriladi?", "ConfigType nima 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") jim NaN.
  • validationSchema (Joi) — aylantiradi, hamma xatoni birdan beradi, lekin tur bermaydi.
  • validate (Zod) — aylantiradi, ConfigService<Env, true> + infer: true bilan tipli; xabar uchun prettifyError.
  • Kanon: registerAs("ilova", () => configYukla(process.env)) — 25-qism sxemasi o'zgarmadi, xizmatlar CONFIG oladi.
  • abortOnError: false + main.ts dagi catch → config xatosida exit 78, boshqa ishga tushish xatosida exit 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS config validatsiyasi: registerAs, ConfigType, Joi va Zod — IlmHamroh