Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. ConfigModule.forRoot — birinchi tajriba
- 2.1 O'rnatish va eng sodda shakl
- 2.2 Sozlamalar jadvali
- 2.3 Tajriba: qiymatlar qanday keladi
- 2.4 Kim ustun: fayl yoki muhit?
- 2.5 cache: true
- 3. Qiymat qayerdan keladi — sxema va model
- 3.1 Manbalar tartibi
- 3.2 JS modelida
- 4. get va tiplar
- 4.1 Generic — yolg'on va'da
- 4.2 Kalit nomidagi xato
- 5. forRootAsync — sozlamani boshqa modulga berish
- 6. Express'da qanday edi, Nest'da qanday
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): kim yutadi?
- 2-mashq (o'rta): modelga expandVariables
- 3-mashq (qiyin): cache va getOrThrow bilan xizmat
- 10. Loyiha qadami: config ConfigModule ichida
- 10.1 Paket
- 10.2 bogliqliklar.module.ts
- 10.3 Uch kichik o'zgarish
- 10.4 Test: yangi xulq
- 10.5 Tekshiruv
- 11. Real ishda
- Xulosa
- Manbalar
NestJS config: @nestjs/config, ConfigModule va ConfigService
Qisqacha:
@nestjs/config— sozlamalarni (config) Nest'ning DI konteyneriga olib kiradigan rasmiy paket.ConfigModule.forRoot().envfaylini o'qiydi vaConfigServiceprovider'ini beradi; istalgan xizmat uni constructor'da olib,config.get("PORT")yokiconfig.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.getvagetOrThrowfarqini,getnega 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-apida config'niConfigModuleichiga 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:
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
npm install --save-exact @nestjs/config@12.0.1Versiya — 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:
@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:
# 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=4000Modul shunday sozlandi:
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:
@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:
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 existHar qatorni o'qiymiz:
PORT: 4000 string— ikki narsa. Birinchisi:.env.localdagi4000yutdi,.envdagi3000emas.envFilePathmassivida birinchi fayl ustun: bir kalit ikki faylda bo'lsa, oldingisidagi qoladi. Ikkinchisi: turistring..env— matn fayl, muhit o'zgaruvchilari ham doim satr (Environment o'zgaruvchilar).DB_URL—${DB_HOST}o'rniga127.0.0.1qo'yildi. BuexpandVariables: trueishi. Usiz${DB_HOST}so'zma-so'z qoladi (pastda ko'ramiz).YOQ: undefined— bunday kalit yo'q,getjimundefinedqaytardi.YOQ (sukut)—getning ikkinchi argumenti — sukut qiymati.TASHQI: muhitdan—ConfigServicefaqat fayldan emas,process.envdan ham o'qiydi.getOrThrow— kalit yo'q bo'lsa xato tashlaydi. Xabar tarjimasi: "Konfiguratsiya kaliti "YOQ" mavjud emas".
Maslahat: Majburiy sozlama uchun doim
getOrThrow.getning jimundefinedi keyinroq, boshqa joyda, tushunarsiz xato bo'lib chiqadi:undefinedport,undefinedparol 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:
PORT: 5000
DB_URL: postgres://${DB_HOST}:5432/bahor
fayl yo'q — DB_HOST: 127.0.0.1PORT: 5000— muhit o'zgaruvchisi fayldan ustun. Bu to'g'ri: serverda (Docker, systemd) qiymatlar muhit orqali beriladi,.envesa faqat lokal qulaylik. 25-qismdaginode --env-fileham shunday ishlaydi (--env-file).DB_URL—${DB_HOST}ochilmadi:expandVariablesyoqilmagan.- Uchinchi qator — kutilmagan narsa. Ikkinchi modul mavjud bo'lmagan
cfg/yoq.envni o'qidi, lekinDB_HOSTbaribir bor. Sababi: birinchi modul.envdagi qiymatlarniprocess.envga yozib qo'ygan. Birinchi tajribada ham shunday edi — oxiridaprocess.env.PORTni chiqardik va4000keldi.
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
cache false: SOZ = ikkinchi
cache true: SOZ = birinchiTajribada 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)..envdaPORT=3000,.env.localdaPORT=4000, muhitdaPORTyo'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:
// 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:
4000 string
sukut
5000
Configuration key "SMS_KALIT" does not existconfigYig 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:
// ❌ tip "number" deydi, qiymat esa "4000" (satr)
const port = this.config.get<number>("PORT");
const keyingi = port + 1; // "40001" — satr birlashdiget<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:
this.config.get("PROT"); // undefined — xato yo'qHarf 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:
.envdaHSTS=false. Kodif (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):
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:
SMS kalit: namuna-kalitHaqiqiy 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:
forRootAsyncichidagetOrThrowishlating. 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.
.envgit'da. Eng ko'p uchraydigan sizib chiqish..gitignoreda.envbo'lsin, repoda faqat.env.example— qiymatsiz namuna (Sirlarni boshqarish).- Config'ni logga yozish. "Ishga tushdim, sozlamalar: {...}" qatori butun
DATABASE_URLni parol bilan chiqaradi. KanondaconfigYuklaxato xabarida qiymatning o'zini chiqarmaydi — faqat kalit nomi va qoida (→ at PORT). process.envga yozish. «Kim ustun: fayl yoki muhit?» bo'limidagi tajriba ko'rsatdi:ConfigModule.envqiymatlariniprocess.envga qo'yadi. Ilovachild_processbilan tashqi dastur ishga tushirsa, u sukut bo'yicha butunprocess.envni — 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;
getaylantirmaydi. - undefined —
getjim; xato kerak bo'lsagetOrThrow.
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
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:
postgres://${DB_HOST}:5432/bahor
postgres://127.0.0.1:5432/bahor
postgres://db:5432/bahor
redis://:6379Uchinchi 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
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:
cache false: SOZ = ikkinchi
cache true: SOZ = birinchi
Configuration key "PORT" does not existNatija «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
npm install --save-exact @nestjs/config@12.0.110.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):
// 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-qismdagiconfigYukla(process.env)ni chaqiradi: Zod tekshiruvi, tiplar, muzlatish — hammasi saqlandi. Test tayyorconfigbersa — o'shani oladi.registerAs,ilova.KEYvaConfigTypeni keyingi darsda to'liq ochamiz.CONFIGprovider'i — xizmatlar avvalgidek@Inject(CONFIG)bilan tipliConfigoladi. Ularning kodi o'zgarmaydi.- Fabrika zanjiri —
LOGCONFIGga,XABARCHIesaCONFIGvaLOGga tayanadi. Nest ularni to'g'ri tartibda yaratadi (DI va IoC konteyner). Oldin bu tartibnimain.tsqo'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:
- 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):
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
npm run tip
npm run lint
npm testBiz 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.envqanday 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
.envfayli 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,getOrThrowemas?" va "buget<number>aslida satr emasmi?".
Xulosa
ConfigModule.forRoot().envni o'qiydi vaConfigServiceni beradi;isGlobal— hamma modulda.- Ustunlik: muhit o'zgaruvchisi >
envFilePathdagi birinchi fayl > keyingilari. getjimundefinedva doim satr qaytaradi;get<number>aylantirmaydi; majburiy sozlama —getOrThrow.expandVariables—${BOSHQA};cache— birinchi qiymatni eslab qoladi;ConfigModule.envqiymatlariniprocess.envga ham yozadi.- Boshqa modulga sozlama —
forRootAsync+inject: [ConfigService]+useFactory. - Kanon:
ConfigModuleichida 25-qism Zod tekshiruvi (registerAs+configYukla), xizmatlarga — tipliCONFIG.
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
- NestJS hujjati — Configuration (
forRoot,envFilePath,expandVariables,cache,getOrThrow, async konfiguratsiya): https://docs.nestjs.com/techniques/configuration @nestjs/confignpm sahifasi: https://www.npmjs.com/package/@nestjs/config- Node.js —
--env-file: https://nodejs.org/api/cli.html#--env-fileconfig - Kanon (IlmHamroh, 2026-10-27):
vazifalar-apiconfig/log/kontekst qadami —BogliqliklarModule(ConfigModule+registerAs+ ZodconfigYukla),@nestjs/config12.0.1.ConfigServicetajribalari (fayllar tartibi, muhit ustunligi,expandVariables,cache,forRootAsync) va oraliq holat tekshiruvi — 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!