IlmHamroh
JavaScript Full-stack/30-qism. NestJS27/65-dars19 daqiqa
Mundarija (31)

NestJS config: @nestjs/config, ConfigModule va ConfigService

Qisqacha: @nestjs/config — sozlamalarni (config) Nest'ning DI konteyneriga olib kiradigan rasmiy paket. ConfigModule.forRoot() .env faylini o'qiydi va ConfigService provider'ini beradi; istalgan xizmat uni constructor'da olib, config.get("PORT") yoki config.getOrThrow("SMS_KALIT") bilan qiymat o'qiydi. Muhim tuzoq: env qiymati doim satr — get("PORT") "4000" qaytaradi, son emas. Shuning uchun real loyihada config ishga tushishda tekshiriladi va tipli obyektga aylantiriladi — buni keyingi darsda qilamiz.

Bu darsda

  • ConfigModule.forRoot() ning asosiy sozlamalarini (isGlobal, envFilePath, expandVariables, cache, ignoreEnvFile) haqiqiy chiqish bilan bilasiz.
  • ConfigService.get va getOrThrow farqini, get nega satr qaytarishini tushuntirasiz.
  • Qiymat qayerdan kelishini — muhit o'zgaruvchisi, .env.local, .env — tartibi bilan ayta olasiz.
  • Boshqa modulga sozlamani forRootAsync + inject: [ConfigService] bilan berasiz.
  • vazifalar-api da config'ni ConfigModule ichiga ko'chirishni boshlaysiz.

Oldin bilishingiz kerak: Environment o'zgaruvchilar va .env, Env validatsiyasi va tipli config obyekti, Custom provider'lar, Dynamic modullar, Global enhancer'lar va bajarilish tartibi.

O'tgan darsda vazifalar-api ning global pipe, guard va filtri DI orqali ulandi. Endi ilovaning eng birinchi bog'liqligiga — sozlamalarga — yetib keldik. Bu dars va keyingi uchtasi bitta loyiha qadami: config, log va so'rov konteksti Nest usuliga o'tadi.

1. Nega bu kerak?

25-qismda sozlamalarni bitta faylga yig'gandik — config.ts. U process.env ni Zod bilan tekshiradi va muzlatilgan, tipli obyekt qaytaradi (Env validatsiyasi). Bu obyektni main.ts yaratib, kerakli joyga parametr qilib uzatardi.

Hozir, Nest'ga ko'chgan vazifalar-api da ham shunday. main.ts config'ni o'qiydi, keyin ishgaTushir ga sakkizta alohida qiymat beradi:

ts
const { server, manzil, app } = await ishgaTushir({
  port: config.PORT,
  host: config.HOST,
  bogliqliklar: {
    config,
    corsOriginlar: config.CORS_ORIGINS,
    tz: config.APP_TZ,
    trustProxy: config.TRUST_PROXY,
    hsts: config.HSTS,
    log,
    // ... xabarchi, nestLogi
  },
});

Yangi sozlama qo'shsangiz, uni uch joyda yozasiz: config.ts, main.ts va ilova.ts. Bittasini unutsangiz — qiymat jim yo'qoladi. Nest'da esa ilovaning hamma obyektlari DI orqali keladi. Sozlama nega istisno bo'lishi kerak?

O'xshatish: oshxonadagi menyu taxtasi. Har oshpazga narxlarni alohida qog'ozda bermaysiz — hammasi devordagi bitta taxtaga qaraydi. Narx o'zgarsa, taxtada bir marta o'zgartirasiz. ConfigService — shu taxta: kimga sozlama kerak bo'lsa, constructor'da so'raydi.

2. ConfigModule.forRoot — birinchi tajriba

2.1 O'rnatish va eng sodda shakl

bash
npm install --save-exact @nestjs/config@12.0.1

Versiya — kanonimizdagi (@nestjs/config 12.0.1, NestJS 12.2.0 bilan). Paket ichida .env ni o'qish uchun dotenv ishlatiladi — 25-qismda ko'rgan kutubxona.

Eng sodda ulash — ildiz modulda:

ts
@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}

forRoot() — dinamik modul: sozlama bilan modul qaytaradi. U ishga tushishda loyiha ildizidagi .env ni o'qiydi va ConfigService provider'ini ro'yxatga qo'yadi. isGlobal: true — @Global modul kabi: boshqa modullar ConfigModule ni import qilmasdan ConfigService ni oladi.

2.2 Sozlamalar jadvali

Sozlama Nima qiladi Sukut
isGlobal ConfigService hamma modulda ko'rinadi false
envFilePath qaysi fayl(lar)ni o'qish .env
ignoreEnvFile faylni umuman o'qimaslik false
expandVariables ${BOSHQA} ni ochish false
cache o'qilgan qiymatni eslab qolish false
load o'z config fabrikalaringiz []

Yana ikkitasi — validationSchema va validate — ishga tushishda tekshirish uchun. Ular keyingi darsning mavzusi.

2.3 Tajriba: qiymatlar qanday keladi

Biz alohida demo loyihada sinadik (biz ishga tushirdik: NestJS 12.2.0, @nestjs/config 12.0.1, Node 24.21.0). Ikki fayl:

text
# cfg/.env
PORT=3000
DB_HOST=127.0.0.1
DB_URL=postgres://${DB_HOST}:5432/bahor
SMS_KALIT=namuna-kalit

# cfg/.env.local
PORT=4000

Modul shunday sozlandi:

ts
ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: ["cfg/.env.local", "cfg/.env"],
  expandVariables: true,
})

Ishga tushishdan oldin kodda process.env.TASHQI = "muhitdan" yozildi — bu "muhit o'zgaruvchisi" o'rnida. Keyin xizmat ichida:

ts
@Injectable()
class Korsat {
  constructor(private readonly config: ConfigService) {}
  chiqar(): void {
    const port = this.config.get("PORT");
    console.log("PORT:", port, typeof port);
    console.log("DB_URL:", this.config.get("DB_URL"));
    console.log("YOQ:", this.config.get("YOQ"));
    console.log("YOQ (sukut):", this.config.get("YOQ", "sukut"));
    console.log("TASHQI:", this.config.get("TASHQI"));
    try {
      this.config.getOrThrow("YOQ");
    } catch (e) {
      console.log("getOrThrow:", (e as Error).message);
    }
  }
}

Natija:

text
PORT: 4000 string
DB_URL: postgres://127.0.0.1:5432/bahor
YOQ: undefined
YOQ (sukut): sukut
TASHQI: muhitdan
getOrThrow: Configuration key "YOQ" does not exist

Har qatorni o'qiymiz:

  • PORT: 4000 string — ikki narsa. Birinchisi: .env.local dagi 4000 yutdi, .env dagi 3000 emas. envFilePath massivida birinchi fayl ustun: bir kalit ikki faylda bo'lsa, oldingisidagi qoladi. Ikkinchisi: turi string. .env — matn fayl, muhit o'zgaruvchilari ham doim satr (Environment o'zgaruvchilar).
  • DB_URL — ${DB_HOST} o'rniga 127.0.0.1 qo'yildi. Bu expandVariables: true ishi. Usiz ${DB_HOST} so'zma-so'z qoladi (pastda ko'ramiz).
  • YOQ: undefined — bunday kalit yo'q, get jim undefined qaytardi.
  • YOQ (sukut) — get ning ikkinchi argumenti — sukut qiymati.
  • TASHQI: muhitdan — ConfigService faqat fayldan emas, process.env dan ham o'qiydi.
  • getOrThrow — kalit yo'q bo'lsa xato tashlaydi. Xabar tarjimasi: "Konfiguratsiya kaliti "YOQ" mavjud emas".

Maslahat: Majburiy sozlama uchun doim getOrThrow. get ning jim undefined i keyinroq, boshqa joyda, tushunarsiz xato bo'lib chiqadi: undefined port, undefined parol bilan bazaga ulanish.

2.4 Kim ustun: fayl yoki muhit?

Ikkinchi tajriba. Endi process.env.PORT = "5000" ni oldin qo'ydik, keyin .env (unda PORT=3000) o'qildi. expandVariables yo'q:

text
PORT: 5000
DB_URL: postgres://${DB_HOST}:5432/bahor
fayl yo'q — DB_HOST: 127.0.0.1
  • PORT: 5000 — muhit o'zgaruvchisi fayldan ustun. Bu to'g'ri: serverda (Docker, systemd) qiymatlar muhit orqali beriladi, .env esa faqat lokal qulaylik. 25-qismdagi node --env-file ham shunday ishlaydi (--env-file).
  • DB_URL — ${DB_HOST} ochilmadi: expandVariables yoqilmagan.
  • Uchinchi qator — kutilmagan narsa. Ikkinchi modul mavjud bo'lmagan cfg/yoq.env ni o'qidi, lekin DB_HOST baribir bor. Sababi: birinchi modul .env dagi qiymatlarni process.env ga yozib qo'ygan. Birinchi tajribada ham shunday edi — oxirida process.env.PORT ni chiqardik va 4000 keldi.

Demak, ConfigModule .env ni o'qibgina qolmaydi, process.env ni ham o'zgartiradi. Kichik ilovada bu qulay. Lekin bu yashirin yon ta'sir: testlar bir-biriga ta'sir qiladi, bola jarayonlar (child_process) .env dagi sirlarni meros oladi. Loyiha qadamida bu sababli faylni Nest'ga o'qitmaymiz.

2.5 cache: true

text
cache false: SOZ = ikkinchi
cache true: SOZ = birinchi

Tajribada process.env.SOZ = "birinchi" qo'yildi, bir marta get("SOZ") o'qildi, keyin process.env.SOZ = "ikkinchi" ga o'zgartirildi. cache: false da get har safar process.env ga qaraydi — yangi qiymat ko'rindi. cache: true da birinchi o'qilgan qiymat eslab qolindi. Prodakshnda sozlama ish davomida o'zgarmaydi, shuning uchun cache: true — process.env ga har murojaatdan qutulish. Sozlamani ish vaqtida "o'zgartirish" kerak bo'lsa — ilovani qayta ishga tushirish to'g'ri yo'l.

Tekshirib ko'ring: envFilePath: [".env", ".env.local"] yozildi (tartib teskari). .env da PORT=3000, .env.local da PORT=4000, muhitda PORT yo'q. get("PORT") nima qaytaradi?

Javob

"3000" (satr). Massivdagi birinchi fayl ustun — endi u .env. Shuning uchun lokal ustidan yozadigan fayl (.env.local) doim birinchi yoziladi. Muhitda PORT bo'lganida esa ikkala fayl ham yutqazardi.

3. Qiymat qayerdan keladi — sxema va model

3.1 Manbalar tartibi

flowchart LR
  E["process.env (muhit)"] --> CS["ConfigService"]
  L[".env.local"] --> CS
  F[".env"] --> CS
  CS --> X["VazifalarService"]
  CS --> S["SmsModule (forRootAsync)"]
  CS --> P["main.ts: PORT"]

Uchta manba bitta ConfigService ga quyiladi. Ustunlik: muhit > envFilePath dagi birinchi fayl > keyingi fayl. O'ng tomonda iste'molchilar — ular manbani bilmaydi, faqat kalit bo'yicha so'raydi. 25-qismdagi qoida saqlanadi: kod process.env ni o'zi o'qimaydi.

3.2 JS modelida

Qoidani brauzerda ishlaydigan kichik modelda takrorlaymiz. Bu Nest kodi emas — ConfigService ning g'oyasi, dotenv va DI'siz:

js
// fayllar — envFilePath tartibida; env — process.env o'rnida
function configYig(fayllar, env) {
  const qiymat = {};
  for (const fayl of [...fayllar].reverse()) {
    Object.assign(qiymat, fayl); // keyingisi oldingisini bosadi
  }
  return { ...qiymat, ...env }; // muhit hammasidan ustun
}

class ConfigXizmati {
  #q;
  constructor(q) { this.#q = q; }
  get(kalit, sukut) { return this.#q[kalit] ?? sukut; }
  getOrThrow(kalit) {
    if (!(kalit in this.#q)) {
      throw new Error(`Configuration key "${kalit}" does not exist`);
    }
    return this.#q[kalit];
  }
}

const local = { PORT: "4000" };
const asosiy = { PORT: "3000", DB_HOST: "127.0.0.1" };
const c = new ConfigXizmati(configYig([local, asosiy], {}));
console.log(c.get("PORT"), typeof c.get("PORT"));
console.log(c.get("YOQ", "sukut"));
const muhit = { PORT: "5000" };
const m = new ConfigXizmati(configYig([local, asosiy], muhit));
console.log(m.get("PORT"));
try {
  c.getOrThrow("SMS_KALIT");
} catch (e) {
  console.log(e.message);
}

Konsolda:

text
4000 string
sukut
5000
Configuration key "SMS_KALIT" does not exist

configYig da fayllar teskari tartibda qo'shiladi: oxirgi fayl birinchi yoziladi, birinchi fayl uni bosadi. Shunda birinchi fayl ustun bo'ladi — Nest'dagidek. Model soddalashtirilgan: unda expandVariables, cache va process.env ga yozish yo'q. Kengaytirishni mashqda qo'shasiz.

4. get va tiplar

4.1 Generic — yolg'on va'da

TypeScript'da shunday yozish mumkin:

ts
// ❌ tip "number" deydi, qiymat esa "4000" (satr)
const port = this.config.get<number>("PORT");
const keyingi = port + 1; // "40001" — satr birlashdi

get<number> — faqat tip tasdiqlash, aylantirish emas. ConfigService qiymatni o'zgartirmaydi. Tajribadagi PORT: 4000 string qatori shuni ko'rsatdi. Natijada port + 1 qo'shish emas, satr birlashtirish bo'ladi — "40001". Xato kompilyatsiyada ham, ishga tushishda ham chiqmaydi.

To'g'ri yo'l — qiymatni ishga tushishda tekshirib, aylantirish. 25-qismda buni Zod bilan qilgandik (Butun(3000) — faqat raqamlar, keyin Number). Nest'da ham shu yo'l: validate yoki load + fabrika. Keyingi darsda ikkalasini ko'ramiz.

4.2 Kalit nomidagi xato

Ikkinchi zaif joy — kalit oddiy satr:

ts
this.config.get("PROT"); // undefined — xato yo'q

Harf adashsa, get jim undefined beradi. 25-qismdagi Config obyektida esa config.PROT — TS2339 tip xatosi. Bu kichik farq loyihada katta ahamiyatga ega: sozlamalar soni 20 dan oshgan (config.ts dagi EnvSxemasi ni sanang).

Tekshirib ko'ring: .env da HSTS=false. Kod if (this.config.get("HSTS")) { ... } deb tekshiradi. Shart bajariladimi?

Javob

Ha, bajariladi. get("HSTS") "false" satrini qaytaradi, bo'sh bo'lmagan satr esa if da rost (truthy). Shuning uchun 25-qismda Rost sxemasi bor: z.enum(["true", "false"]).transform(x => x === "true").

5. forRootAsync — sozlamani boshqa modulga berish

Ko'p kutubxona modullari (TypeOrmModule, JwtModule, CacheModule) sozlamani forRoot(...) da kutadi. Lekin forRoot chaqirilgan paytda — modul fayli yuklanganda — .env hali o'qilmagan bo'lishi mumkin. Yechim: shu modulning forRootAsync varianti. U sozlamani fabrika provider orqali oladi (Custom provider'lar):

ts
SmsModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (c: ConfigService) => ({
    kalit: c.getOrThrow<string>("SMS_KALIT"),
  }),
}),

inject — fabrikaga qaysi provider'lar kerak. Nest avval ConfigService ni yaratadi, keyin fabrikani chaqiradi. Tajribamizda SmsModule ni o'zimiz yozdik — Dynamic modullar darsidagi naqsh: forRootAsync { provide: SMS, inject, useFactory } provider'ini qaytaradi. Natija:

text
SMS kalit: namuna-kalit

Haqiqiy kutubxonalarda ham aynan shu shakl. TypeORM darsida TypeOrmModule.forRootAsync ni shunday ulaymiz; vazifalar-api da esa keyingi ikkinchi darsda LoggerModule.forRootAsync paydo bo'ladi.

Diqqat: forRootAsync ichida getOrThrow ishlating. Sozlama yo'q bo'lsa, xato ilova ishga tushayotganda chiqadi — birinchi SMS yuborilganda emas.

6. Express'da qanday edi, Nest'da qanday

25–27 (Express) Nest + @nestjs/config
.env ni kim o'qiydi node --env-file-if-exists dotenv (yoki baribir Node)
Sozlama kimga yetadi parametr bilan uzatiladi DI: constructor'da so'raladi
Tip Config (Zod) get — any / satr
Xato qachon ishga tushishda (fail-fast) get da jim, getOrThrow da — so'ralganda

Jadvalning oxirgi ikki qatori muhim. ConfigService ni "ochiq" ishlatsak, 25-qismdagi yutuqlarni — tip va fail-fast'ni — yo'qotamiz. Shuning uchun kanon ConfigModule ni oladi, lekin ichiga 25-qismdagi Zod tekshiruvini qo'yadi. Har xizmat esa ConfigService.get("…") emas, tayyor tipli Config obyektini oladi.

Nima yutamiz: sozlama DI orqali keladi, main.ts dagi sakkiz parametrli uzatish yo'qoladi (keyingi darsda), testda istalgan qiymatni berish oson. Nima to'laymiz: bitta yangi prod bog'liqlik. Butun loyiha qadami (config, log, kontekst) uchun prod bog'liqliklar soni 15 dan 18 ga oshdi (kanon o'lchovi, IlmHamroh, 2026-10).

7. Hujumchi nigohi

Config — sirlar yashaydigan joy: baza paroli, SMS kaliti, to'lov tokeni.

  • .env git'da. Eng ko'p uchraydigan sizib chiqish. .gitignore da .env bo'lsin, repoda faqat .env.example — qiymatsiz namuna (Sirlarni boshqarish).
  • Config'ni logga yozish. "Ishga tushdim, sozlamalar: {...}" qatori butun DATABASE_URL ni parol bilan chiqaradi. Kanonda configYukla xato xabarida qiymatning o'zini chiqarmaydi — faqat kalit nomi va qoida (→ at PORT).
  • process.env ga yozish. «Kim ustun: fayl yoki muhit?» bo'limidagi tajriba ko'rsatdi: ConfigModule .env qiymatlarini process.env ga qo'yadi. Ilova child_process bilan tashqi dastur ishga tushirsa, u sukut bo'yicha butun process.env ni — sirlar bilan — meros oladi.

Kanondagi himoya: ignoreEnvFile: true — .env ni Node o'zi o'qiydi (--env-file-if-exists), Nest ikkinchi marta o'qimaydi va process.env ga yangi narsa qo'shmaydi.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
port + 1 → "40001" get<number> aylantirmaydi tekshirib aylantiring (keyingi dars)
HSTS=false, lekin yoqilgan "false" satri rost "true"/"false" ni boolean'ga o'giring
undefined bilan bazaga ulanish get jim, kalitda harf xatosi getOrThrow yoki tipli obyekt
.env.local ishlamaydi envFilePath da u ikkinchi ustun faylni birinchi yozing
${DB_HOST} so'zma-so'z qoldi expandVariables yo'q expandVariables: true
Nest can't resolve dependencies … ConfigService isGlobal yo'q, modul import qilinmagan isGlobal: true yoki imports

Oxirgi qatordagi xato Modullar darsidagi ko'rinish qoidasining o'zi: ConfigModule global bo'lmasa, uni ishlatadigan har modul import qilishi kerak.

9. Mashqlar

Mashqlar kurs/mashqlar/30/27-nestjs-config/ da.

1-mashq (oson): kim yutadi?

envFilePath: [".env.test", ".env"]. .env.test da SAQLASH=xotira, .env da SAQLASH=postgres va PORT=3000. Muhitda PORT=8080. To'ldiring:

  • get("SAQLASH") —
  • get("PORT") —
  • get("PORT") ning turi —
  • get("REDIS_URL") (hech qayerda yo'q) —
Yechim
  • xotira — massivdagi birinchi fayl ustun.
  • 8080 — muhit o'zgaruvchisi har qanday fayldan ustun.
  • string — env qiymatlari doim satr; get aylantirmaydi.
  • undefined — get jim; xato kerak bo'lsa getOrThrow.

2-mashq (o'rta): modelga expandVariables

Darsdagi configYig ga uchinchi parametr qo'shing: kengaytir (boolean). Rost bo'lsa, har qiymatdagi ${NOM} yig'ilgan qiymatlardagi NOM bilan almashsin; topilmasa — bo'sh satr. Ishora: replace(/\$\{(\w+)\}/g, (_, nom) => …) (RegExp) va ?? operatori.

Yechim
js
function configYig(fayllar, env, kengaytir = false) {
  const qiymat = {};
  for (const fayl of [...fayllar].reverse()) {
    Object.assign(qiymat, fayl);
  }
  const hammasi = { ...qiymat, ...env };
  if (!kengaytir) return hammasi;
  const natija = {};
  for (const [k, v] of Object.entries(hammasi)) {
    natija[k] = v.replace(/\$\{(\w+)\}/g,
      (_, nom) => hammasi[nom] ?? "");
  }
  return natija;
}

const fayl = {
  DB_HOST: "127.0.0.1",
  DB_URL: "postgres://${DB_HOST}:5432/bahor",
  KESH: "redis://${REDIS_HOST}:6379",
};
console.log(configYig([fayl], {}).DB_URL);
console.log(configYig([fayl], {}, true).DB_URL);
console.log(configYig([fayl], { DB_HOST: "db" }, true).DB_URL);
console.log(configYig([fayl], {}, true).KESH);

Konsolda:

text
postgres://${DB_HOST}:5432/bahor
postgres://127.0.0.1:5432/bahor
postgres://db:5432/bahor
redis://:6379

Uchinchi qator muhim: kengaytirish yig'ilgandan keyin bo'ladi, shuning uchun muhitdagi DB_HOST=db fayldagi URL ichiga ham tushdi. To'rtinchi qator — topilmagan o'zgaruvchi jim bo'sh satr bo'ldi va manzil buzildi (redis://:6379). Shuning uchun kengaytirilgan qiymatlar ham ishga tushishda tekshirilishi kerak.

3-mashq (qiyin): cache va getOrThrow bilan xizmat

ConfigXizmati ni ikkinchi parametr { cache } bilan qayta yozing. Konstruktor manba obyektini (process.env o'rnida) havola sifatida saqlasin, nusxa emas. cache: false — get har safar manbadan o'qiydi; cache: true — birinchi o'qilgan qiymatni Map da eslab qoladi (Map). getOrThrow keshni ham, manbani ham hurmat qilsin. Sinov: manbaga SOZ = "birinchi", bir marta o'qing, "ikkinchi" ga o'zgartiring, yana o'qing — ikkala rejimda.

Yechim
js
class ConfigXizmati {
  #manba;
  #kesh = new Map();
  #cache;
  constructor(manba, { cache = false } = {}) {
    this.#manba = manba;
    this.#cache = cache;
  }
  get(kalit, sukut) {
    if (this.#cache && this.#kesh.has(kalit)) {
      return this.#kesh.get(kalit);
    }
    const q = this.#manba[kalit];
    if (this.#cache && q !== undefined) this.#kesh.set(kalit, q);
    return q ?? sukut;
  }
  getOrThrow(kalit) {
    const q = this.get(kalit);
    if (q === undefined) {
      throw new Error(`Configuration key "${kalit}" does not exist`);
    }
    return q;
  }
}

for (const cache of [false, true]) {
  const env = { SOZ: "birinchi" };
  const c = new ConfigXizmati(env, { cache });
  c.get("SOZ");
  env.SOZ = "ikkinchi";
  console.log(`cache ${cache}: SOZ = ${c.get("SOZ")}`);
}
const c = new ConfigXizmati({}, { cache: true });
try {
  c.getOrThrow("PORT");
} catch (e) {
  console.log(e.message);
}

Konsolda:

text
cache false: SOZ = ikkinchi
cache true: SOZ = birinchi
Configuration key "PORT" does not exist

Natija «cache: true» bo'limidagi Nest tajribasi bilan bir xil. Nozik joy: undefined keshga yozilmaydi — aks holda keyinroq qo'shilgan kalit ham "yo'q" bo'lib qolardi. Nest'ning haqiqiy kodi bundan murakkabroq, lekin g'oya shu.

10. Loyiha qadami: config ConfigModule ichida

Bu dars va keyingi uchtasi bitta qadamning qismlari: config, log va so'rov konteksti Nest usuliga o'tadi. Commit — So'rov konteksti: nestjs-cls darsida. Bu darsda — BogliqliklarModule ichiga ConfigModule kiradi.

10.1 Paket

bash
npm install --save-exact @nestjs/config@12.0.1

10.2 bogliqliklar.module.ts

Oldin bu modul tashqarida yaratilgan obyektlarni useValue bilan berardi: config, log, sozlamalar — hammasi main.ts dan. Endi u o'zi yaratadi: config — ConfigModule orqali, qolgani — config'dan fabrika provider bilan. Faylning yangi to'liq matni (kanon; uzun qatorlar va izohlar telefon uchun bo'lindi, ma'no o'zgarmagan):

ts
// bogliqliklar.module.ts — ilovaning umumiy bog'liqliklari
// (global): config, log, xabarchi, sozlamalar, idempotentlik ombori.
// Config @nestjs/config orqali: registerAs("ilova") fabrikasi
// 25-qism configYukla() (Zod, fail-fast) ni chaqiradi; qolgan
// provider'lar config'dan fabrika bilan quriladi. Testlar tayyor
// qiymat berishi mumkin (config, log, xabarchi, tz)
import { Global, Module } from "@nestjs/common";
import type { DynamicModule, Provider } from "@nestjs/common";
import { ConfigModule, registerAs } from "@nestjs/config";
import type { ConfigType } from "@nestjs/config";
import {
  CONFIG, IDEMPOTENTLIK_OMBORI, ILOVA_SOZLAMALARI, LOG, XABARCHI,
} from "./tokenlar.ts";
import type { IlovaSozlamalari } from "./tokenlar.ts";
import { configYukla } from "./config.ts";
import type { Config } from "./config.ts";
import { joriyLog, logYarat } from "./umumiy/log.ts";
import type { Log } from "./umumiy/log.ts";
import { webhookXabarchi } from "./umumiy/webhook.ts";
import type { Xabarchi } from "./umumiy/webhook.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";

export type TashqiBogliqliklar = {
  /** berilmasa — process.env dan (configYukla); xato bo'lsa
   *  ConfigXatosi — ilova yaratilmaydi */
  config?: Config | undefined;
  log?: Log | undefined;
  xabarchi?: Xabarchi | undefined;
  tz?: string | undefined;
  idempotentlikOmbori: IdempotentlikOmbori;
};

@Global()
@Module({})
export class BogliqliklarModule {
  static forRoot({
    config, log, xabarchi, tz, idempotentlikOmbori,
  }: TashqiBogliqliklar): DynamicModule {
    // Nomlangan bo'lim (namespace): ConfigService.get("ilova")
    // yoki @Inject(ilova.KEY) — ConfigType<typeof ilova> bilan tipli
    const ilova = registerAs(
      "ilova",
      (): Config => config ?? configYukla(process.env),
    );
    const providers: Provider[] = [
      {
        provide: CONFIG,
        useFactory: (c: ConfigType<typeof ilova>): Config => c,
        inject: [ilova.KEY],
      },
      {
        provide: LOG,
        useFactory: (c: Config): Log => log ?? logYarat(c.LOG_LEVEL),
        inject: [CONFIG],
      },
      {
        provide: XABARCHI,
        useFactory: (c: Config, l: Log): Xabarchi | null =>
          xabarchi ??
          (c.WEBHOOK_URL === undefined
            ? null
            : webhookXabarchi(c.WEBHOOK_URL, {
                log: () => joriyLog(l),
              })),
        inject: [CONFIG, LOG],
      },
      {
        provide: ILOVA_SOZLAMALARI,
        useFactory: (c: Config): IlovaSozlamalari => ({
          tz: tz ?? c.APP_TZ,
        }),
        inject: [CONFIG],
      },
      {
        provide: IDEMPOTENTLIK_OMBORI,
        useValue: idempotentlikOmbori,
      },
    ];
    return {
      module: BogliqliklarModule,
      // ignoreEnvFile: .env ni Node o'zi o'qiydi
      // (node --env-file-if-exists=.env) — dotenv ikkinchi marta
      // o'qimasin; isGlobal — ConfigService hamma joyda
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          ignoreEnvFile: true,
          cache: true,
          load: [ilova],
        }),
      ],
      providers,
      exports: [
        CONFIG, LOG, XABARCHI, ILOVA_SOZLAMALARI,
        IDEMPOTENTLIK_OMBORI,
      ],
    };
  }
}

Uch joyga qarang:

  • registerAs("ilova", …) — config'ning nomlangan bo'limi. Uning fabrikasi 25-qismdagi configYukla(process.env) ni chaqiradi: Zod tekshiruvi, tiplar, muzlatish — hammasi saqlandi. Test tayyor config bersa — o'shani oladi. registerAs, ilova.KEY va ConfigType ni keyingi darsda to'liq ochamiz.
  • CONFIG provider'i — xizmatlar avvalgidek @Inject(CONFIG) bilan tipli Config oladi. Ularning kodi o'zgarmaydi.
  • Fabrika zanjiri — LOG CONFIG ga, XABARCHI esa CONFIG va LOG ga tayanadi. Nest ularni to'g'ri tartibda yaratadi (DI va IoC konteyner). Oldin bu tartibni main.ts qo'lda tuzardi.

TashqiBogliqliklar turi ham o'zgardi: sozlamalar o'rniga tz, log va config ixtiyoriy bo'ldi.

10.3 Uch kichik o'zgarish

src/app.module.ts da forRoot ning birinchi qatoridagi tekshiruv o'chadi — endi config process.env dan o'qiladi, uni berish shart emas:

diff
-    if (ombor === undefined && b.config === undefined) throw …

src/ilova.ts da AppModule.forRoot(...) chaqiruvidagi sozlamalar: { tz } o'rniga tz yoziladi. test/marshrutlar.test.ts da ham sozlamalar: { tz: "Asia/Tashkent" } → tz: "Asia/Tashkent".

10.4 Test: yangi xulq

test/ombor-modul.test.ts dagi "ombor ham, config ham yo'q — darhol xato" testi endi noto'g'ri: bunday xato yo'q. Uning o'rniga yangi xulq tekshiriladi (importga ConfigXatosi qo'shiladi):

ts
test("hech narsa berilmasa — config process.env dan; " +
  "env xato — ConfigXatosi, ilova yaratilmaydi", async () => {
  const app = await ilovaYarat({});
  // test muhitida SAQLASH yo'q — xotira
  assert.ok(app.get(VAZIFA_OMBORI) instanceof XotiraOmbori);
  await app.close();
  const asl = process.env["PORT"];
  process.env["PORT"] = "abc";
  try {
    await assert.rejects(
      ilovaYarat({}),
      (x: unknown) =>
        x instanceof ConfigXatosi && /PORT/.test(x.message),
    );
  } finally {
    if (asl === undefined) delete process.env["PORT"];
    else process.env["PORT"] = asl;
  }
});

Test ikki narsani tekshiradi. Birinchisi: hech narsa berilmasa, ilova process.env dan config yasaydi. Ikkinchisi: PORT=abc bo'lsa, registerAs fabrikasidagi configYukla ConfigXatosi tashlaydi va ilova yaratilmaydi. finally — process.env ni asl holiga qaytaradi, boshqa testlar buzilmasin (try/finally).

10.5 Tekshiruv

bash
npm run tip
npm run lint
npm test

Biz holat nusxasida shu o'zgarishlar bilan yurgizdik (biz ishga tushirdik, Postgres'siz): tip 0, lint 0, Vitest — 75 passed | 6 skipped. O'tkazib yuborilganlari — bazaga ulanadigan testlar; Postgres yoqilgan kanon muhitida ular ham o'tadi. main.ts hali config'ni o'zi o'qib, ilovaga beryapti — buni keyingi darsda olib tashlaymiz. Hali commit qilmang.

11. Real ishda

  • Deyarli har Nest loyihasida birinchi import — ConfigModule.forRoot({ isGlobal: true }). Intervyuda "Nest'da .env qanday o'qiladi?" savoliga shu javob kutiladi.
  • Muhitlar: envFilePath: [".env.${NODE_ENV}.local", ".env.${NODE_ENV}", ".env"] — keng tarqalgan naqsh (test, staging uchun alohida fayl). Ustunlik qoidasi — shu darsdagi.
  • Docker va Kubernetes'da .env fayli umuman bo'lmaydi — qiymatlar muhit orqali keladi (33-qismda ko'ramiz). Shuning uchun "muhit fayldan ustun" qoidasi muhim.
  • Review'dagi savol: "nega bu yerda get, getOrThrow emas?" va "bu get<number> aslida satr emasmi?".

Xulosa

  • ConfigModule.forRoot() .env ni o'qiydi va ConfigService ni beradi; isGlobal — hamma modulda.
  • Ustunlik: muhit o'zgaruvchisi > envFilePath dagi birinchi fayl > keyingilari.
  • get jim undefined va doim satr qaytaradi; get<number> aylantirmaydi; majburiy sozlama — getOrThrow.
  • expandVariables — ${BOSHQA}; cache — birinchi qiymatni eslab qoladi; ConfigModule .env qiymatlarini process.env ga ham yozadi.
  • Boshqa modulga sozlama — forRootAsync + inject: [ConfigService] + useFactory.
  • Kanon: ConfigModule ichida 25-qism Zod tekshiruvi (registerAs + configYukla), xizmatlarga — tipli CONFIG.

Keyingi dars: Config validatsiyasi va namespace'lar — registerAs va ConfigType ni ochamiz, Joi va Zod bilan tekshirishni solishtiramiz, main.ts dan config o'qishni olib tashlaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS config: @nestjs/config, ConfigModule va ConfigService — IlmHamroh