Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 1.1 Express'da qanday edi
- 2. DynamicModule obyekti
- 2.1 Tuzilishi
- 2.2 Kanon tajribasidagi obyekt
- 2.3 O'zingiz to'ldiring
- 3. Konventsiyalar
- 3.1 forRootAsync ning uch shakli
- 3.2 Bir modul — bir necha nusxa
- 4. ConfigurableModuleBuilder
- 4.1 Avtomatik forRoot va forRootAsync
- 4.2 Qo'lda yoki quruvchi bilan
- 4.3 O'z forRoot ingiz: to'rt savol
- 5. JS modelida dinamik modul
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): qaysi metod?
- 2-mashq (o'rta): sozlamani tekshiradigan forRoot
- 3-mashq (qiyin): forRootAsync modeli
- 8. Loyiha qadami: OmborModule.forRoot va forRootAsync
- 8.1 Reja
- 8.2 ombor.module.ts
- 8.3 app.module.ts
- 8.4 bogliqliklar.module.ts va ilova.ts
- 8.5 Test: ikkala yo'l
- 8.6 Tekshiruv
- 9. Real ishda
- Xulosa
- Manbalar
NestJS dynamic modullar: forRoot, forRootAsync va ConfigurableModuleBuilder
Qisqacha: Dinamik modul — tarkibi (provider'lari, importlari) ish vaqtida, sozlama bo'yicha hosil bo'ladigan modul. U
@Module({...})o'rniga statik metod qaytarganDynamicModuleobyekti:{ module, providers, imports, exports, global }. Konventsiya:forRoot(sozlama)— butun ilovaga bir marta,forRootAsync({ imports, inject, useFactory })— sozlama boshqa provider'dan,register/forFeature— har iste'molchiga alohida. Bu metodlarni qo'lda yozish yokiConfigurableModuleBuilderbilan avtomatik yasash mumkin.
Bu darsda
DynamicModuleobyekti nimadan iboratini va statik moduldan farqini bilasiz.forRoot,forRootAsync,register,forFeaturekonventsiyalarini farqlaysiz.ConfigurableModuleBuildernima yasashini kanon tajribasida ko'rasiz.- Modul daraxti dinamik modullar bilan qanday yig'ilishini JS modelida takrorlaysiz.
vazifalar-apiningOmborModuleiga ikki yo'l berasiz: tayyor ombor (testlar) yoki config bo'yicha fabrika.
Oldin bilishingiz kerak: Circular dependency, ModuleRef va lazy modullar, Custom provider'lar, Modullar, Getter/setter va static a'zolar.
O'tgan darsda halqalardan qochishni gaplashdik. Endi oldingi uch darsning bo'laklari birlashadi: Custom provider'lar darsida yozilgan ombor fabrikasi hali ulanmagan edi. Bu darsda u ulanadi — dinamik modul orqali.
1. Nega bu kerak?
Custom provider'lar darsidagi OmborModule oddiy, statik modul edi: @Module({ providers: [...] }). Uning tarkibi kodda qotgan — har doim config bo'yicha fabrika.
Lekin bizga ikki xil xulq kerak:
- Prodakshn (
main.ts): ombor config bo'yicha, Nest ichida yaratilsin. - Testlar: test tayyor
XotiraOmboriyoki ataylab "buzuq" omborni beradi va Nest aynan shuni ishlatsin.
Statik modulda buni qilib bo'lmaydi. Bitta @Module — bitta tarkib. Kerak bo'lgani — modulni qaytaradigan funksiya: "menga tayyor ombor ber" yoki "config'dan yasa" deb chaqiriladi va mos tarkibni qaytaradi.
O'xshatish: mebel do'konida tayyor shkaf (statik modul) va buyurtma shkaf (dinamik modul). Buyurtmada siz o'lcham va rang berasiz, usta shunga mos shkaf yig'adi. Model bitta, natija — buyurtmaga qarab.
1.1 Express'da qanday edi
25–27-qismlarda bu muammo bor edi va biz uni oddiy funksiya bilan hal qilgandik. ilovaYarat({ ombor }) — omborni argument sifatida oladi. main.ts omborni config bo'yicha o'zi yaratib, shu funksiyaga berardi. Testlar esa new XotiraOmbori() ni berardi. Ya'ni "kim yaratadi" degan qaror chaqiruvchida edi.
Nest'da ilova qo'lda yig'ilmaydi — uni konteyner modullardan yig'adi. Shuning uchun tanlov ham modul darajasiga ko'chadi. Dinamik modul — aynan o'sha ilovaYarat({ ombor }) g'oyasi, faqat modul shaklida: argument oladi va shunga mos tarkib qaytaradi.
| Express (25–27) | Nest (shu dars) | |
|---|---|---|
| Tanlov qayerda | ilovaYarat argumenti |
OmborModule.forRoot yoki forRootAsync |
| Ombor kim yaratadi | main.ts yoki test |
test yoki Nest fabrikasi |
| Yutuq | sodda, ko'rinib turadi | konteyner ulaydi, yopishni ham o'z zimmasiga oladi |
| Narx | qo'lda uzatish | bitta qo'shimcha tushuncha — dinamik modul |
2. DynamicModule obyekti
2.1 Tuzilishi
Siz buni allaqachon ko'rgansiz — BogliqliklarModule.forRoot(...) Provider'lar darsidan beri loyihada:
@Global()
@Module({})
export class BogliqliklarModule {
static forRoot(b: TashqiBogliqliklar): DynamicModule {
return {
module: BogliqliklarModule,
providers: [/* … */],
exports: [/* … */],
};
}
}DynamicModule — oddiy obyekt:
| Maydon | Ma'nosi |
|---|---|
module |
qaysi klass — majburiy; modulning "ismi" |
providers, exports, imports, controllers |
@Module dagidek |
global |
true — @Global() bilan bir xil |
Bu maydonlar klassning @Module({...}) dagi tarkibiga qo'shiladi. Shuning uchun dekoratordagi @Module({}) bo'sh — hamma narsa metoddan keladi. static — metod klassning o'ziga tegishli, nusxaga emas (static a'zolar): OmborModule.forRoot(...) — new siz.
2.2 Kanon tajribasidagi obyekt
Kanon tajribasi tayyor forRoot natijasini ekranga chiqargan. Qatordagi to'rt qiymatga qarang — ular jadvaldagi maydonlar:
DynamicModule obyekti (forRoot natijasi): module=XabarModule, global=true, providers=1, exports=0Bu — ConfigurableModuleBuilder yasagan forRoot ning natijasi (pastda batafsil). Obyekt oddiy: modul nomi, global belgisi, bitta provider (sozlamalar). Nest uni imports ichida ko'rganda, oddiy modul klassi kabi daraxtga qo'shadi.
exports=0 ga e'tibor bering. Tajribadagi modul o'z sozlamasini tashqariga bermaydi — uni faqat ichidagi xizmat ishlatadi. Bu to'g'ri: sozlama — modulning ichki ishi.
2.3 O'zingiz to'ldiring
Quyidagi forRoot tayyor ombor oladi va uni hamma modulga ko'rinadigan qiladi. Bo'sh joylarni to'ldiring:
- Qaytariladigan obyektdagi majburiy maydon:
: OmborModule - Provider:
{ provide: VAZIFA_OMBORI, : ombor } - Boshqa modullar ko'rishi uchun:
: [VAZIFA_OMBORI] - Import qilmasdan ham ko'rinsin:
global:
Bu — loyiha qadamidagi OmborModule.forRoot ning deyarli o'zi. Farqi bitta: u yerda global o'rniga klassdagi @Global() dekoratori ishlatilgan — natija bir xil.
3. Konventsiyalar
Metod nomlari Nest ekotizimida kelishilgan — nomga qarab ma'nosini bilasiz:
| Metod | Ma'nosi | Misol |
|---|---|---|
forRoot(sozlama) |
butun ilovaga bir marta, ildizda | TypeOrmModule.forRoot({...}) |
forRootAsync({...}) |
xuddi shu, sozlama boshqa provider'dan | JwtModule.registerAsync, TypeOrmModule.forRootAsync |
forFeature(...) |
har feature modulga o'z qismi | TypeOrmModule.forFeature([Vazifa]) |
register(sozlama) |
har iste'molchiga alohida nusxa | HttpModule.register({ timeout }) |
forRoot + forFeature juftligi ko'p uchraydi: ildizda baza ulanishi bir marta (forRoot), har feature modul o'z jadvallari uchun ombor oladi (forFeature). Bu naqshni TypeORM bilan NestJS darsida ko'rasiz.
forRootAsync ning tipik shakli — o'tgan darslardagi fabrika:
XabarModule.forRootAsync({
imports: [SozlamaModule],
inject: [Sozlamalar],
useFactory: async (s: Sozlamalar) => ({
prefiks: s.prefiks(),
daraja: "warn" as const,
}),
})imports — fabrika uchun kerak provider qaysi moduldan; inject — fabrika argumentlari; useFactory — sozlamani qaytaradi (async ham mumkin).
3.1 forRootAsync ning uch shakli
useFactory — eng ko'p uchraydigan shakl, lekin yagona emas. Nest kutubxonalarining forRootAsync (yoki registerAsync) metodi odatda uch shaklni qabul qiladi:
| Shakl | Ma'nosi | Qachon |
|---|---|---|
useFactory + inject |
sozlamani funksiya qaytaradi | ko'p holatda — eng sodda |
useClass |
Nest klass nusxasini yaratadi, sozlamani uning metodi beradi | sozlama mantiqi katta, o'z bog'liqliklari bor |
useExisting |
xuddi shunday, lekin mavjud provider'ni oladi | sozlama klassi boshqa joyda allaqachon bor |
useClass shaklida klass maxsus metodni bajaradi. ConfigurableModuleBuilder yasagan modulda bu metodning sukut nomi — create(); u sozlama obyektini qaytaradi. Tashqi kutubxonalarda nom boshqacha bo'lishi mumkin (masalan, createTypeOrmOptions()) — hujjatiga qarang.
Bu uch shaklning mohiyati bitta: sozlamani qachon va kim hisoblashini konteynerga topshirish. Sizga kerak provider'lar (config, log) tayyor bo'lgach, Nest fabrikani yoki klass metodini chaqiradi.
3.2 Bir modul — bir necha nusxa
forRoot va register farqi qog'ozda kichik ko'rinadi. Amalda u "ilovada nechta ulanish ochiladi" degan savolga javob beradi. Nest dinamik modulni klassi va tarkibi bo'yicha taniydi. Tarkib bir xil bo'lsa — bitta nusxa; boshqacha bo'lsa — alohida nusxa.
Kichik modelda ko'raylik. Konteyner modul nusxalarini kalit bo'yicha saqlaydi: klass nomi + sozlama. Brauzerda ishlaydi:
const nusxalar = new Map();
let ulanishlar = 0;
// Nest'ning modul kaliti g'oyasi: klass + tarkib
function yukla(dinamik) {
const kalit = dinamik.module.name +
JSON.stringify(dinamik.sozlama);
if (!nusxalar.has(kalit)) {
ulanishlar++;
nusxalar.set(kalit, `ulanish #${ulanishlar}`);
}
return nusxalar.get(kalit);
}
class HttpModule {
static register(sozlama) {
return { module: HttpModule, sozlama };
}
}
// ikki feature modul, har biri o'z timeout'i bilan
console.log("vazifalar:",
yukla(HttpModule.register({ timeout: 3000 })));
console.log("eksport:",
yukla(HttpModule.register({ timeout: 30000 })));
// uchinchisi birinchisi bilan bir xil sozlama
console.log("teglar:",
yukla(HttpModule.register({ timeout: 3000 })));
console.log("jami ulanish:", ulanishlar);Konsolda:
vazifalar: ulanish #1
eksport: ulanish #2
teglar: ulanish #1
jami ulanish: 2Nima bo'ldi? Birinchi ikki modul turli sozlama berdi — ikki alohida nusxa. Uchinchi modul (teglar) birinchisi bilan aynan bir xil sozlama berdi va o'sha nusxani oldi. Bu register uchun bu to'g'ri xulq: har iste'molchi o'ziga mos HTTP klientini oladi.
Endi baza ulanishini tasavvur qiling. Har feature modul DatabaseModule.forRoot({ url }) ni o'zi chaqirsa va sozlamalar biroz farq qilsa (masalan, biri max: 10, boshqasi max: 20) — ikki pool ochiladi. Postgres ulanishlari ikki baravar ko'payadi va hech qanday xato chiqmaydi. Shuning uchun forRoot qoidasi qat'iy: ildizda, bir marta. Feature modullar esa tayyor provider'ni oladi.
Model soddalashtirilgan: haqiqiy Nest kalitni JSON.stringify bilan emas, modul metama'lumotidan hisoblaydi. G'oya esa aynan shu.
Tekshirib ko'ring:
VazifalarModulevaEksportModulehar biriRedisModule.forRoot({ url: REDIS_URL })ni import qildi, ikkalasida URL bir xil. Ikki ulanish ochiladimi?
Javob
Bizning modelda — yo'q: kalit bir xil, nusxa bitta. Lekin bunga tayanmang. Ertaga kimdir bitta modulda { url, db: 1 } yozsa — kalit o'zgaradi va jim ikkinchi ulanish ochiladi. Ishonchli yo'l — forRoot ni AppModule da bir marta chaqirish va provider'ni eksport qilish (yoki modulni global qilish).
4. ConfigurableModuleBuilder
4.1 Avtomatik forRoot va forRootAsync
forRoot va forRootAsync ni har modulda qo'lda yozish takroriy ish. Nest buning uchun quruvchi beradi:
type XabarSozlamasi = { prefiks: string; daraja?: "info" | "warn" };
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<XabarSozlamasi>()
.setClassMethodName("forRoot")
.setExtras(
{ isGlobal: false },
(def, extras) => ({ ...def, global: extras.isGlobal }),
)
.build();
@Injectable()
class Xabarchi {
constructor(
@Inject(MODULE_OPTIONS_TOKEN) private readonly s: XabarSozlamasi,
) {}
yoz(x: string) {
return `[${this.s.prefiks}:${this.s.daraja ?? "info"}] ${x}`;
}
}
@Module({ providers: [Xabarchi], exports: [Xabarchi] })
class XabarModule extends ConfigurableModuleClass {}Qatorma-qator:
new ConfigurableModuleBuilder<XabarSozlamasi>()— sozlama turi bilan quruvchi..setClassMethodName("forRoot")— metod nomi (sukut —register). NatijadaforRootvaforRootAsyncpaydo bo'ladi..setExtras({ isGlobal: false }, …)— sozlamaga qo'shimcha maydon (isGlobal), u modulningglobalmaydoniga aylanadi.MODULE_OPTIONS_TOKEN— sozlamalar token'i; xizmat uni@Injectbilan oladi.extends ConfigurableModuleClass— modul klassi tayyor statik metodlarni meros oladi.
Kanon natijasi (Vitest muhitida):
forRoot: [sinxron:info] salom
forRootAsync (imports + inject): [vazifalar:warn] salom
OPTIONS_TYPE/ASYNC_OPTIONS_TYPE — faqat tur uchun (ish vaqtida qiymati): object, objectforRoot({ prefiks: "sinxron" }) — sozlama to'g'ridan. forRootAsync({...}) — sozlama boshqa moduldagi provider'dan, fabrika orqali. OPTIONS_TYPE va ASYNC_OPTIONS_TYPE — faqat TypeScript turlari uchun (typeof XabarModule.OPTIONS_TYPE); ish vaqtida ular ma'nosiz bo'sh obyekt.
4.2 Qo'lda yoki quruvchi bilan
vazifalar-api da kanon OmborModule ni qo'lda yozdi. Sabab — o'qish oson: ikki metod, har biri nima qaytarishi ko'rinib turibdi. Quruvchi esa kuchli, lekin sehrli: forRootAsync qayerdan kelgani, useClass va useExisting variantlari ham borligi kodda ko'rinmaydi.
Qoida: modul bir-ikki joyda ishlatiladigan bo'lsa — qo'lda. Boshqa jamoalar foydalanadigan kutubxona moduli bo'lsa — quruvchi: u useFactory, useClass, useExisting shakllarining hammasini bepul beradi va standart ko'rinishda.
Tekshirib ko'ring: Hamkasbingiz quruvchida
.setClassMethodName("forRoot")ni yozishni unutdi.AppModuleda esaXabarModule.forRoot({ prefiks: "x" })chaqirilgan. TypeScript nima deydi va qaysi metodlar bor?
Javob
TypeScript xato beradi: forRoot degan statik metod yo'q. Sukut nom — register, demak quruvchi register va registerAsync ni yasagan. Yo chaqiruvni XabarModule.register(...) ga o'zgartiring, yo quruvchiga setClassMethodName("forRoot") qo'shing. Nomni ma'noga qarab tanlang: butun ilovaga bir marta bo'lsa — forRoot, har iste'molchiga alohida bo'lsa — register.
4.3 O'z forRoot ingiz: to'rt savol
Qo'lda yozasizmi yoki quruvchi bilanmi — har dinamik modul uchun to'rt savolga javob bering. Ular keyingi bo'limlardagi xatolarning deyarli hammasini oldindan yopadi.
- Sozlama to'g'rimi?
forRootistalgan obyektni qabul qiladi. Bo'shprefiksyoki yo'q URL bilan ilova ishga tushsa, xato birinchi so'rovda chiqadi — ancha kech. Metod boshida tekshiring va aniq xabar bilan xato tashlang. - Sukut qiymatlar qayerda?
{ daraja: "info", ...sozlama }— bitta joyda. Ular xizmat ichiga tarqalib ketsa, "sukut nima edi?" degan savolga javob topish qiyin. - Nima eksport qilinadi? Provider modul ichida yaratiladi, lekin
exportsda bo'lmasa — boshqa modullar uni ko'rmaydi. Faqat tashqariga kerak narsani eksport qiling; ichki yordamchilar (masalan,SAQLASH) yashirin qolsin. - Global bo'lsinmi?
global: truequlay: har feature modul import qilmasdan oladi. Lekin "bu provider qayerdan keldi?" degan savol qiyinlashadi. Global — butun ilovaga kerak, bitta nusxali narsalar uchun (config, log, ombor).
vazifalar-api dagi OmborModule shu savollarga shunday javob beradi: tekshiruv — AppModule.forRoot da, sukut yo'q (ombor yoki config shart), eksport — faqat VAZIFA_OMBORI, global — ha.
5. JS modelida dinamik modul
Nest daraxtni yig'ganda klass ham, DynamicModule obyekti ham bir xil ko'rinishga keltiriladi. Buni kichik modelda takrorlaymiz — brauzerda ishlaydi:
// Modul — klass (static meta) yoki { module, providers, imports }
const tarkib = (m) =>
typeof m === "function" ? { module: m, ...m.meta } : m;
function yigish(ildiz, natija = []) {
const t = tarkib(ildiz);
for (const i of t.imports ?? []) yigish(i, natija);
const p = (t.providers ?? []).join(", ") || "—";
natija.push(`${t.module.name}: ${p}`);
return natija;
}
class OmborModule {
static forRoot(ombor) {
return { module: OmborModule, providers: [`OMBOR=${ombor}`] };
}
static forRootAsync() {
return {
module: OmborModule,
providers: ["SAQLASH (fabrika)", "OMBOR=SAQLASH.ombor"],
};
}
}
class VazifalarModule {
static meta = { providers: ["VazifalarService"] };
}
class AppModule {
static forRoot({ ombor }) {
return {
module: AppModule,
imports: [
ombor
? OmborModule.forRoot(ombor)
: OmborModule.forRootAsync(),
VazifalarModule,
],
};
}
}
const tayyor = AppModule.forRoot({ ombor: "XotiraOmbori" });
console.log(yigish(tayyor).join("\n"));
console.log("---");
console.log(yigish(AppModule.forRoot({})).join("\n"));Konsolda:
OmborModule: OMBOR=XotiraOmbori
VazifalarModule: VazifalarService
AppModule: —
---
OmborModule: SAQLASH (fabrika), OMBOR=SAQLASH.ombor
VazifalarModule: VazifalarService
AppModule: —Bitta AppModule.forRoot — ikki xil daraxt. Birinchisida ombor tayyor (useValue), ikkinchisida fabrika. tarkib funksiyasi — Nest ichidagi ishning qisqa shakli: klass bo'lsa metama'lumotdan, obyekt bo'lsa maydonlardan. Model soddalashtirilgan: haqiqiy Nest dekoratordagi va obyektdagi maydonlarni birlashtiradi, bir modulni ikki marta yig'maydi va global modullarni alohida hisobga oladi.
flowchart TB
A["AppModule.forRoot(b)"] --> B["BogliqliklarModule.forRoot: log, xabarchi, sozlamalar, config"]
A --> C{"b.ombor bormi?"}
C -- ha --> D["OmborModule.forRoot(ombor): useValue"]
C -- yo'q --> E["OmborModule.forRootAsync(): fabrika"]
A --> V["VazifalarModule"]
A --> X["EksportModule"]Diagramma loyiha qadamidagi tanlovni ko'rsatadi: testlar yuqori tarmoqdan (tayyor ombor), main.ts keyinroq pastki tarmoqdan (fabrika) o'tadi.
Tekshirib ko'ring:
OmborModule.forRoot(a)vaOmborModule.forRoot(b)ikki xil feature modulda import qilinsa, ilovada nechtaOmborModulebo'ladi?
Javob
Ikkita. Nest dinamik modulni module klassi va uning tarkibi (provider'lar) bo'yicha ajratadi; har xil tarkibli ikki forRoot — ikki alohida modul nusxasi. Shuning uchun forRoot ildizda bir marta chaqiriladi, feature modullar esa u eksport qilgan provider'ni (bizda — global VAZIFA_OMBORI) oladi. Har feature'ga alohida nusxa kerak bo'lsa — register/forFeature naqshi.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
forRoot ichidagi provider boshqa modulda ko'rinmaydi |
exports yoki global unutilgan |
exports: [TOKEN], kerak bo'lsa global: true |
| Ikki ulanish, ikki pool | forRoot bir necha feature modulda chaqirilgan |
ildizda bir marta |
module maydoni yo'q |
DynamicModule noto'g'ri obyekt |
module: KlassNomi majburiy |
forRootAsync fabrikasi provider'ni topmaydi |
fabrika uchun imports berilmagan |
imports: [SozlamaModule] |
| Sozlama tekshirilmay qoladi | forRoot har qanday obyektni qabul qiladi |
metod boshida tekshiruv va aniq xato |
Ikkitasini batafsil ko'raylik — ular eng ko'p vaqt oladi.
Provider ko'rinmaydi. Nest bu holatda DI va IoC konteyner darsidagi tanish xatoni beradi: Nest can't resolve dependencies of the …. Xabar "provider yo'q" deydi, lekin aslida provider bor — u boshqa modul ichida yopiq qolgan. Birinchi tekshiradigan joy: dinamik modulning exports maydoni. Ikkinchisi: iste'molchi modul uni imports ga qo'shganmi yoki modul globalmi.
Ikki pool. Bu xato hech qanday xabar bermaydi. Ilova ishlaydi, testlar o'tadi. Faqat bazada ulanishlar soni kutilganidan ko'p bo'ladi, keyinroq esa "too many connections" chiqadi. Topish usuli — Lifecycle hook'lar darsida ko'radigan pg_stat_activity so'rovi: unda bitta ilovadan nechta seans borligi ko'rinadi.
7. Mashqlar
Mashqlar kurs/mashqlar/30/12-dinamik-modul/ da.
1-mashq (oson): qaysi metod?
- Baza ulanishi — butun ilovaga bir marta:
- Xuddi shu, lekin URL
ConfigServicedan: - Har feature modulga o'z jadvallari:
DynamicModuledagi majburiy maydon:- Quruvchi yaratadigan sozlamalar tokeni:
Yechim
- forRoot — ildizda bir marta.
- forRootAsync —
inject: [ConfigService],useFactory. - forFeature — feature modul darajasida.
- module — qaysi klass.
- MODULE_OPTIONS_TOKEN — xizmat sozlamani shu token bilan oladi.
2-mashq (o'rta): sozlamani tekshiradigan forRoot
XabarModule.forRoot(sozlama) ni JS'da yozing. U sukut qiymatlarni qo'shsin (daraja: "info"), prefiks bo'sh satr yoki yo'q bo'lsa Error("XabarModule.forRoot: prefiks kerak") tashlasin va { module, global, providers: [{ provide: "XABAR_SOZLAMASI", useValue }] } qaytarsin. global — sozlama.isGlobal ?? false. Uch holatni sinang. Ishora: spread bilan sukut qiymatlar — { daraja: "info", ...sozlama }.
Yechim
class XabarModule {
static forRoot(sozlama = {}) {
const { isGlobal = false, ...qolgan } = sozlama;
const toliq = { daraja: "info", ...qolgan };
if (!toliq.prefiks) {
throw new Error("XabarModule.forRoot: prefiks kerak");
}
return {
module: XabarModule,
global: isGlobal,
providers: [{ provide: "XABAR_SOZLAMASI", useValue: toliq }],
};
}
}
const d1 = XabarModule.forRoot({ prefiks: "vazifalar" });
console.log(d1.global, JSON.stringify(d1.providers[0].useValue));
const d2 = XabarModule.forRoot({
prefiks: "eksport", daraja: "warn", isGlobal: true,
});
console.log(d2.global, JSON.stringify(d2.providers[0].useValue));
try {
XabarModule.forRoot({ daraja: "warn" });
} catch (e) {
console.log(e.message);
}Konsolda:
false {"daraja":"info","prefiks":"vazifalar"}
true {"daraja":"warn","prefiks":"eksport"}
XabarModule.forRoot: prefiks kerakisGlobal sozlamaga tushmadi — u modul darajasidagi belgi (quruvchidagi setExtras ham aynan shuni qiladi). Xato ilova yig'ilayotganda chiqadi — fail-fast. vazifalar-api dagi AppModule.forRoot ham shunday tekshiruv bilan boshlanadi.
3-mashq (qiyin): forRootAsync modeli
forRootAsync({ inject, useFactory }) qaytaradigan obyekt va uni "ishga tushiradigan" yukla(dinamikModul, tayyorProviderlar) funksiyasini yozing. yukla inject dagi tokenlarni tayyorProviderlar (Map) dan olib, useFactory ni await bilan chaqirsin va XABAR_SOZLAMASI qiymatini qaytarsin. Token topilmasa — Error("<token> topilmadi"). Sinov: CONFIG dan prefiks oladigan async fabrika, keyin CONFIG siz. Ishora: Promise.all, map.
Yechim
class XabarModule {
static forRootAsync({ inject = [], useFactory }) {
return {
module: XabarModule,
providers: [{ provide: "XABAR_SOZLAMASI", inject, useFactory }],
};
}
}
async function yukla(dinamik, tayyor) {
const p = dinamik.providers[0];
const args = p.inject.map((t) => {
if (!tayyor.has(t)) throw new Error(`${String(t)} topilmadi`);
return tayyor.get(t);
});
return p.useFactory(...await Promise.all(args));
}
const CONFIG = Symbol("CONFIG");
const modul = XabarModule.forRootAsync({
inject: [CONFIG],
useFactory: async (config) => ({
prefiks: config.ILOVA, daraja: "warn",
}),
});
const tayyor = new Map([[CONFIG, { ILOVA: "vazifalar" }]]);
console.log(await yukla(modul, tayyor));
try {
await yukla(modul, new Map());
} catch (e) {
console.log(e.message);
}Konsolda:
{ prefiks: 'vazifalar', daraja: 'warn' }
Symbol(CONFIG) topilmadiforRootAsync o'zi hech narsa chaqirmaydi — u faqat retsept qaytaradi (inject + useFactory). Fabrika keyin, konteyner hamma provider'ni yig'ayotganda ishlaydi. vazifalar-api dagi OmborModule.forRootAsync() ham shunday: chaqirilganda baza ochilmaydi, faqat ilova yaratilayotganda.
8. Loyiha qadami: OmborModule.forRoot va forRootAsync
8.1 Reja
Custom provider'lar darsidagi OmborModule dinamik modulga aylanadi va daraxtga ulanadi. AppModule.forRoot qaror qiladi: tayyor ombor berilgan bo'lsa — forRoot(ombor), aks holda — forRootAsync(). BogliqliklarModule dan ombor chiqariladi: endi uning yagona egasi — OmborModule.
Nega ombor BogliqliklarModule dan chiqadi? Qoida: har resursning bitta egasi bo'lsin. Ombor ikki modulda provider bo'lsa, qaysi biri ishlatilishi import tartibiga bog'liq bo'lib qoladi — bunday xatoni topish qiyin. Endi VAZIFA_OMBORI faqat OmborModule da. Kelajakda ombor ulanishlarini yopish ham o'sha yerda bo'ladi.
main.ts bu darsda o'zgarmaydi: u hali omborni o'zi yaratib, tayyor beradi (forRoot yo'li). Fabrika yo'liga o'tish ombor ulanishlarini yopishni talab qiladi — u Lifecycle hook'lar darsida.
8.2 ombor.module.ts
// ombor.module.ts — dinamik modul: VAZIFA_OMBORI provider'ini ikki
// xil beradi.
// forRoot(ombor) — tayyor obyekt (testlar: XotiraOmbori, "buzuq"
// ombor) — useValue, yopish chaqiruvchining ishi;
// forRootAsync() — config bo'yicha (SAQLASH=xotira|fayl|postgres|
// prisma, REDIS_URL): ASYNC fabrika provider — Nest bootstrap
// paytida Promise'ni kutadi (baza ulanishi, migratsiya
// tekshiruvi — fail-fast)
import { Global, Module } from "@nestjs/common";
import type { DynamicModule } from "@nestjs/common";
import { omborYarat } from "./ombor-yarat.ts";
import type { Saqlash } from "./ombor-yarat.ts";
import type { Config } from "./config.ts";
import type { Log } from "./umumiy/log.ts";
import { CONFIG, LOG, VAZIFA_OMBORI } from "./tokenlar.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
const SAQLASH = Symbol("SAQLASH");
@Global()
@Module({})
export class OmborModule {
static forRoot(ombor: VazifaOmbori): DynamicModule {
return {
module: OmborModule,
providers: [{ provide: VAZIFA_OMBORI, useValue: ombor }],
exports: [VAZIFA_OMBORI],
};
}
static forRootAsync(): DynamicModule {
return {
module: OmborModule,
providers: [
// async fabrika: Nest Promise'ni kutadi; xato bo'lsa —
// ilova ishga TUSHMAYDI (EGASI_ID yo'q, baza yo'q,
// migratsiya orqada)
{
provide: SAQLASH,
useFactory: (config: Config, log: Log): Promise<Saqlash> =>
omborYarat(config, log),
inject: [CONFIG, LOG],
},
{
provide: VAZIFA_OMBORI,
useFactory: (s: Saqlash) => s.ombor,
inject: [SAQLASH],
},
],
exports: [VAZIFA_OMBORI],
};
}
}Dekoratordagi @Module({}) endi bo'sh, provider'lar metodlarda. Birinchi metod — forRoot — bitta useValue qo'shadi. Uning ma'nosi: "bu omborni beraman, yaratish va yopish meniki emas". Ikkinchisi — forRootAsync — o'tgan darsdagi fabrikalar aynan, faqat metod ichida. forRootAsync argument olmaydi: unga kerak hamma narsa (CONFIG, LOG) global BogliqliklarModule dan keladi. @Global() klassda qoldi — ikkala variant ham global.
8.3 app.module.ts
// app.module.ts — ildiz modul: ilova shu moduldan boshlab yig'iladi
// (imports daraxti). forRoot(...) — dinamik modul: ombor tayyor
// berilsa (testlar) — shu, aks holda config bo'yicha
// OmborModule.forRootAsync() yaratadi
import { Module } from "@nestjs/common";
import type { DynamicModule } from "@nestjs/common";
import { BogliqliklarModule } from "./bogliqliklar.module.ts";
import type { TashqiBogliqliklar } from "./bogliqliklar.module.ts";
import { OmborModule } from "./ombor.module.ts";
import { VazifalarModule } from "./vazifalar/vazifalar.module.ts";
import { EksportModule } from "./eksport/eksport.module.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
export type IlovaBogliqliklari = TashqiBogliqliklar & {
ombor?: VazifaOmbori | undefined;
};
@Module({})
export class AppModule {
static forRoot(
{ ombor, ...b }: IlovaBogliqliklari,
): DynamicModule {
if (ombor === undefined && b.config === undefined) {
throw new Error("AppModule.forRoot: ombor yoki config kerak");
}
return {
module: AppModule,
imports: [
BogliqliklarModule.forRoot(b),
ombor === undefined
? OmborModule.forRootAsync()
: OmborModule.forRoot(ombor),
VazifalarModule,
EksportModule,
],
};
}
}Uch narsa. { ombor, ...b } — ombor ajratiladi, qolgani BogliqliklarModule ga (rest). Tekshiruv: na ombor, na config — fabrika ishlay olmaydi, darhol aniq xato («Sozlamani tekshiradigan forRoot» mashqidagi naqsh). Va shartli import — modul daraxti endi sozlamaga bog'liq.
8.4 bogliqliklar.module.ts va ilova.ts
BogliqliklarModule dan ombor chiqariladi:
-import {
- CONFIG, ILOVA_SOZLAMALARI, LOG, VAZIFA_OMBORI, XABARCHI,
-} from "./tokenlar.ts";
+import {
+ CONFIG, ILOVA_SOZLAMALARI, LOG, XABARCHI,
+} from "./tokenlar.ts";
import type { IlovaSozlamalari } from "./tokenlar.ts";
import type { Config } from "./config.ts";
-import type { VazifaOmbori } from "./vazifalar/ombor.ts"; export type TashqiBogliqliklar = {
- ombor: VazifaOmbori;
log: Log; static forRoot(
- { ombor, log, xabarchi, sozlamalar, config }: TashqiBogliqliklar,
+ { log, xabarchi, sozlamalar, config }: TashqiBogliqliklar,
): DynamicModule {
const providers: Provider[] = [
- { provide: VAZIFA_OMBORI, useValue: ombor },
{ provide: LOG, useValue: log },Fayl boshidagi izohdan ham "ombor" so'zi ketadi: endi u log, xabarchi va config uchun. ilova.ts da ombor ixtiyoriy bo'ladi:
export type Bogliqliklar = {
- ombor: VazifaOmbori;
- /** to'liq config (main.ts) — DI ichida CONFIG tokeni */
+ /** tayyor ombor (testlar); berilmasa — config bo'yicha
+ * OmborModule.forRootAsync() yaratadi va yopadi */
+ ombor?: VazifaOmbori;
+ /** to'liq config (main.ts) — ombor tanlash uchun */
config?: Config;ilovaYarat qolgani o'zgarmaydi: u { ombor, config, … } ni AppModule.forRoot ga uzatardi — endi ombor yo'q bo'lishi ham mumkin.
8.5 Test: ikkala yo'l
Yangi test/ombor-modul.test.ts — avval ikki test:
// ombor-modul.test.ts — OmborModule.forRootAsync() (async fabrika
// provider, dinamik modul): config bo'yicha ombor DI ichida
// yaratiladi
import { test } from "vitest";
import assert from "node:assert/strict";
import { ilovaYarat } from "../src/ilova.ts";
import { configYukla } from "../src/config.ts";
import { VAZIFA_OMBORI } from "../src/tokenlar.ts";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
test("SAQLASH=xotira: ombor config'dan (fabrika) — XotiraOmbori, " +
"HTTP ishlaydi", async () => {
const app = await ilovaYarat({
config: configYukla({ SAQLASH: "xotira" }),
});
assert.ok(app.get(VAZIFA_OMBORI) instanceof XotiraOmbori);
await app.close();
});
test("ombor ham, config ham yo'q — darhol xato", async () => {
await assert.rejects(ilovaYarat({}), /ombor yoki config kerak/);
});Testlar uchun alohida baza kerak emas: SAQLASH=xotira rejimi fabrika yo'lini xotirada sinaydi. Ikki test ikki savolga javob beradi. Fabrika yo'li umuman ishlaydimi? Va noto'g'ri chaqiruv aniq xato beradimi? Ikkinchi savol muhim: xato xabari keyingi dasturchiga nima qilish kerakligini aytadi.
Birinchi test fabrika yo'lini sinaydi: ombor berilmadi, config bor — forRootAsync ishladi. app.get(VAZIFA_OMBORI) — ilova konteyneridan provider'ni olish (ModuleRef.get ning ilova darajasidagi shakli). Ikkinchi test — AppModule.forRoot dagi tekshiruv. Qolgan 25–27 testlari esa tayyor XotiraOmbori beradi — ular forRoot(ombor) yo'lidan o'tadi va o'zgarmaydi.
8.6 Tekshiruv
npm run tip
npm run lint
npm testBiz kanon nusxasida yurgizdik: tip 0, lint 0; ombor-modul.test, supertest.test, api.test — 12/12 o'tdi. Hali commit qilmang — holat commit'i Reflector va DiscoveryService darsida.
9. Real ishda
- Nest ekotizimi dinamik modullarga qurilgan:
ConfigModule.forRoot,TypeOrmModule.forRootAsync,BullModule.registerQueue,JwtModule.register. Bu darsdan keyin ularning hujjatini o'qish ancha oson. - O'z kutubxonangiz (masalan, kompaniyadagi SMS yoki to'lov moduli) —
ConfigurableModuleBuilderbilan; foydalanuvchilar standartforRoot/forRootAsyncni kutadi. - Testlarda dinamik modul almashtirish nuqtasi: prodakshnda fabrika, testda tayyor obyekt —
vazifalar-apidagidek. - Kod review'da yangi
forRootko'rsangiz, to'rt savolni bering: sozlama tekshiriladimi, sukutlar qayerda, nima eksport qilinadi, global bo'lishi kerakmi. Feature modul ichidaforRootchaqiruvi — darhol savol: "bu ikkinchi ulanish ochmaydimi?". - Intervyu: "Dynamic module nima?", "
forRootvaforFeaturefarqi?", "forRootAsyncqachon kerak?".
Xulosa
- Dinamik modul — statik metod qaytargan
DynamicModuleobyekti; tarkibi sozlamaga bog'liq. modulemajburiy;providers,imports,exports,global—@Moduledagidek.forRoot— ildizda bir marta,forRootAsync— sozlama provider'dan,forFeature/register— har iste'molchiga.ConfigurableModuleBuilderforRoot/forRootAsyncvaMODULE_OPTIONS_TOKENni avtomatik yasaydi; kichik ichki modulda qo'lda yozish o'qish uchun osonroq.- Nest dinamik modulni klass va tarkib bo'yicha taniydi: turli sozlamali ikki
forRoot— ikki nusxa, ya'ni ikki ulanish. Shuning uchunforRootildizda bir marta chaqiriladi. forRootAsyncuch shaklda keladi:useFactory,useClass,useExisting; hammasida sozlamani konteyner kerakli lahzada hisoblaydi.vazifalar-api:OmborModule.forRoot(ombor)(testlar) vaforRootAsync()(config);AppModule.forRoottanlaydi.
Keyingi dars: Lifecycle hook'lar — onModuleInit dan onApplicationShutdown gacha; ombor pool'i ilova yopilganda yopiladi va main.ts fabrika yo'liga o'tadi.
Manbalar
- NestJS hujjati — Dynamic modules (
forRoot,forRootAsync,ConfigurableModuleBuilder): https://docs.nestjs.com/fundamentals/dynamic-modules - Kanon tajribasi (IlmHamroh, 2026-10-27):
ConfigurableModuleBuilderbilanforRoot/forRootAsync, NestJS 12.2.0, Vitest muhitida. Loyiha qadami 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!