Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. DI va IoC
- 2.1 Ikki tushuncha
- 2.2 Nest qanday "ko'radi"
- 3. O'zimiz o'qib ko'ramiz
- 3.1 Metama'lumotda nima bor
- 3.2 Haqiqiy Nest metama'lumoti
- 3.3 Oddiy turlar: string → String
- 4. Injection token
- 4.1 Uch xil token
- 4.2 Nega Symbol, satr emas
- 5. Nest'ning DI xatolari
- 5.1 Provider ro'yxatda yo'q
- 5.2 Interfeys token'siz — chalg'ituvchi xabar
- 5.3 Token bor, provider yo'q
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): metama'lumotni toping
- 2-mashq (o'rta): qattiq bog'lanishni yechish
- 3-mashq (qiyin): satr token to'qnashuvi
- 8. Loyiha qadami: CONFIG tokeni
- 8.1 Reja
- 8.2 tokenlar.ts
- 8.3 bogliqliklar.module.ts
- 8.4 ilova.ts va main.ts
- 8.5 Tekshiruv
- 9. Real ishda
- Xulosa
- Manbalar
NestJS DI va IoC konteyner: design:paramtypes, tokenlar va bog'liqlik xatolari
Qisqacha: Bog'liqlikni tashqaridan berish (DI) — klass o'ziga kerak obyektni o'zi yaratmaydi, constructor orqali oladi. IoC konteyner — kim kimga kerakligini bilib, obyektlarni yaratadigan va ulaydigan qism. Nest bog'liqliklarni TypeScript yozib qoldirgan
design:paramtypesmetama'lumotidan o'qiydi (emitDecoratorMetadata+reflect-metadata). Klass turi o'zi token bo'ladi; interfeys esa ish vaqtida yo'q — metama'lumotdaObjectqoladi, shuning uchun unga injection token (bog'liqlik kaliti:Symbol, satr) va@Inject(TOKEN)kerak.
Bu darsda
- Qattiq bog'lanish (tight coupling) nima uchun zararli ekanini misolda ko'rasiz.
- IoC va DI farqini o'z so'zingiz bilan tushuntirasiz.
design:paramtypesni o'zingiz o'qib, kichik DI konteyner yozasiz — dekoratorlar bilan, haqiqiy TypeScript'da.- Injection token turlarini (klass,
Symbol, satr) va qachon qaysi biri kerakligini bilasiz. - Nest'ning uch xil DI xatosini haqiqiy matni bilan o'qiysiz, shu jumladan chalg'ituvchisini.
Oldin bilishingiz kerak: Provider'lar va service'lar, Legacy dekoratorlar va reflect-metadata, Symbol, express.Router va qatlamli loyiha tuzilmasi, Request lifecycle.
O'tgan darsda so'rov qaysi tartibda o'tishini ko'rdik. Bu darsda bir qadam orqaga — so'rovdan oldinga — qaytamiz: ilova ishga tushayotganda Nest controller va xizmatlarni qanday yaratadi va ularni qanday ulaydi.
1. Nega bu kerak?
Tasavvur qiling, VazifalarService omborni o'zi yaratadi:
class VazifalarService {
readonly #ombor = new PgOmbori(pgPool(process.env.DATABASE_URL));
ol(id: number) {
return this.#ombor.ol(id);
}
}Bu kod ishlaydi. Lekin uchta muammo bor:
- Test. Xizmatni sinash uchun haqiqiy Postgres kerak. Soxta omborni berib bo'lmaydi — u ichkarida yaratilgan.
- Almashtirish.
SAQLASH=xotirayoki Prisma omboriga o'tish uchun xizmat kodini o'zgartirish kerak. - Yashirin bog'liqlik. Constructor'ga qarab, xizmatga baza kerakligini bilib bo'lmaydi. Faylni oxirigacha o'qish kerak.
Bu — qattiq bog'lanish (tight coupling): klass o'z bog'liqligining aniq turini biladi va uni o'zi yaratadi. Teskarisi — bo'sh bog'lanish (loose coupling): klass faqat interfeysni (VazifaOmbori) biladi va uni tashqaridan oladi. 25-qismdagi vazifaXizmati(ombor) aynan shunday edi (qatlamlar darsi).
O'xshatish: devorga mahkam payvandlangan dazmol va rozetkaga ulanadigan dazmol. Birinchisini almashtirish uchun devorni buzasiz. Ikkinchisi faqat "rozetka standartini" biladi — istalgan dazmolni ulaysiz.
2. DI va IoC
2.1 Ikki tushuncha
Bog'liqlik (dependency) — klassga ishlashi uchun kerak bo'lgan boshqa obyekt. VazifalarService uchun ombor va log.
Bog'liqlikni tashqaridan berish (dependency injection, DI) — bog'liqlikni klass ichida yaratmasdan, tashqaridan (odatda constructor orqali) berish. Bu — usul. Uni qo'lda ham qilish mumkin: 25-qismda ilova.ts hamma narsani o'zi ulardi.
Boshqaruvni teskari berish (inversion of control, IoC) — obyektlarni yaratish va ulash ishini freymvork bajaradi, sizning kodingiz emas. Bu — tamoyil. Ba'zan "Gollivud tamoyili" deyiladi: "Bizga qo'ng'iroq qilmang, kerak bo'lsa o'zimiz qo'ng'iroq qilamiz".
Bu tamoyilni siz 25-qismda ham ko'rgansiz, faqat nomini aytmagandik. Express handler'ini siz chaqirmaysiz: r.get("/vazifalar", handler) deb ro'yxatdan o'tkazasiz, so'rov kelganda Express o'zi chaqiradi. Boshqaruv — "qachon chaqirish" — freymvorkda. Nest bu g'oyani bir qadam uzaytiradi: endi obyektlarni yaratish ham freymvorkda.
IoC konteyner — shu tamoyilni amalga oshiruvchi qism: u har klassning bog'liqliklarini biladi, kerakli tartibda yaratadi va tayyor nusxalarni saqlaydi. NestJS nima va nega darsida 25 qatorlik JS modelini yozgandik.
| DI qo'lda (25-qism) | DI + IoC konteyner (Nest) | |
|---|---|---|
| Kim yaratadi | siz, ilova.ts da |
konteyner |
| Tartib | siz yozgan | konteyner hisoblaydi |
| Bog'liqlik ro'yxati | funksiya parametrlari | constructor turlari (metama'lumot) |
| Xato qachon | ishga tushishda (TypeError) yoki hech qachon | ishga tushishda, aniq xabar bilan |
2.2 Nest qanday "ko'radi"
Bizning JS modelimizda bog'liqliklarni qo'lda yozgandik: k.qosh(VazifalarXizmati, [Ombor]). Nest'da siz faqat constructor yozasiz. Ro'yxatni kompilyator yozadi.
tsconfig.json da emitDecoratorMetadata: true bo'lsa, TypeScript dekoratorli klass uchun JavaScript'ga qo'shimcha qator qo'shadi. Nest CLI darsida build qilingan fayldan ko'rgandik:
AppController = __decorate([
Controller(),
__metadata("design:paramtypes", [AppService])
], AppController);__metadata("design:paramtypes", [AppService]) — "bu klass constructor'ining parametr turlari: [AppService]". reflect-metadata paketi bu yozuvni klassga bog'lab saqlaydi, Nest esa Reflect.getMetadata("design:paramtypes", Klass) bilan o'qiydi.
Ikki shart bor: emitDecoratorMetadata yoqiq bo'lishi va klassda kamida bitta dekorator bo'lishi. Provider'lar va service'lar darsidagi @Injectable() siz klass — ikkinchi shart buzilgan holat edi.
3. O'zimiz o'qib ko'ramiz
3.1 Metama'lumotda nima bor
Quyidagi blok haqiqiy TypeScript: dekoratorlar va reflect-metadata bilan. Saytda uning «Ishga tushir» tugmasi yo'q — dekorator metama'lumoti build talab qiladi. Biz uni tsc bilan JavaScript'ga o'girib, Node'da yurgizdik; natija pastda. Siz ham o'z kompyuteringizda shunday qila olasiz. Injectable va Inject — Nest'dagilarning kichik nusxasi:
// @ts-options: {"experimentalDecorators": true, "emitDecoratorMetadata": true}
import "reflect-metadata";
interface Ombor { ol(): string }
const OMBOR = Symbol("OMBOR");
const TOKENLAR = "tokenlar";
// Nest'ning @Injectable va @Inject g'oyasi — kichik nusxa
const Injectable = (): ClassDecorator => () => {};
function Inject(token: symbol): ParameterDecorator {
return (klass, _kalit, indeks) => {
const eski: Record<number, symbol> =
Reflect.getMetadata(TOKENLAR, klass) ?? {};
Reflect.defineMetadata(
TOKENLAR, { ...eski, [indeks]: token }, klass,
);
};
}
@Injectable()
class Soat {
hozir() { return "09:00"; }
}
@Injectable()
class Xizmat {
constructor(
readonly soat: Soat,
@Inject(OMBOR) readonly ombor: Ombor,
) {}
}
type Klass = new (...a: never[]) => object;
const nomi = (t: unknown) =>
typeof t === "function" ? t.name : String(t);
function yarat(K: Klass, royxat: Map<unknown, () => unknown>) {
const turlar: unknown[] =
Reflect.getMetadata("design:paramtypes", K) ?? [];
const tokenlar: Record<number, symbol> =
Reflect.getMetadata(TOKENLAR, K) ?? {};
const argumentlar = turlar.map((tur, i) => {
const token = tokenlar[i] ?? tur; // @Inject ustun
const fabrika = royxat.get(token);
if (!fabrika) {
throw new Error(`${K.name} (index [${String(i)}]): ` +
`${nomi(token)} topilmadi`);
}
return fabrika();
});
return new K(...(argumentlar as never[]));
}
console.log(Reflect.getMetadata("design:paramtypes", Xizmat)
.map(nomi));
const royxat = new Map<unknown, () => unknown>([
[Soat, () => new Soat()],
]);
try {
yarat(Xizmat, royxat);
} catch (e) {
console.log((e as Error).message);
}
royxat.set(OMBOR, () => ({ ol: () => "xotira" }));
const x = yarat(Xizmat, royxat) as Xizmat;
console.log(x.soat.hozir(), x.ombor.ol());Konsolda:
[ 'Soat', 'Object' ]
Xizmat (index [1]): Symbol(OMBOR) topilmadi
09:00 xotiraUch qatorni o'qing:
[ 'Soat', 'Object' ]— kompilyator ikki parametrni yozdi. Birinchisi — haqiqiy klassSoat. Ikkinchisi —Object: interfeysOmborish vaqtida yo'q, TypeScript uning o'rnigaObjectqo'ydi.index [1]—@Inject(OMBOR)metama'lumotdagiObjectni token bilan almashtirdi, lekin bunday token ro'yxatda hali yo'q. Xabar Nest'nikiga o'xshaydi — kim, nechanchi argument, qaysi token.- Token ro'yxatga qo'shilgach, konteyner ikkala bog'liqlikni topdi.
Bu — Nest DI'ning butun mexanizmi, kichik shaklda. Haqiqiy Nest qo'shimcha ravishda modul chegarasini (qaysi provider qayerda ko'rinadi), singleton'ni (tayyor jadvali) va aylanma bog'liqlikni hisobga oladi.
3.2 Haqiqiy Nest metama'lumoti
Kanon xuddi shuni haqiqiy @nestjs/common dekoratorlari bilan qildi (kanon tajribasi, Vitest muhitida):
1) emitDecoratorMetadata — tsc/oxc constructor turlarini yozib qo'yadi:
design:paramtypes(Xizmat) = [ 'Soat' ]
design:paramtypes(InterfeysliXizmat) = [ 'Object' ]
self:paramtypes(TokenliXizmat) = [{"index":1,"param":"YOQ"},{"index":0,"param":"Symbol(OMBOR)"}]self:paramtypes — Nest'ning o'z kaliti: @Inject(...) shu yerga "index → token" juftini yozadi. Bizning modeldagi TOKENLAR aynan uning o'rni. Nest bog'liqlikni qidirganda self:paramtypes dagi tokenni design:paramtypes dagi turdan ustun qo'yadi. oxc — Vitest ishlatadigan Vite kompilyatori; u ham, tsc ham bu metama'lumotni yozadi.
Tekshirib ko'ring:
constructor(private readonly log: Log)—Logbuimport type { Log } from "./log.ts"bilan olingan pino logger turi.design:paramtypesda nima bo'ladi?
Javob
Object. Log — TypeScript turi, ish vaqtida mavjud emas; import type esa kompilyatsiyadan keyin butunlay o'chadi. Shuning uchun vazifalar-api da @Inject(LOG) yoziladi. Klass bo'lganda ham import type bilan olinsa, xuddi shu muammo chiqadi — buni Nest xabari ham eslatadi (keyingi bo'lim).
3.3 Oddiy turlar: string → String
Interfeys Object ga aylanishini ko'rdik. Oddiy turlar-chi — string, number, birlashma ("yangi" | "bajarildi"), massiv? Kompilyator ular uchun ham nimadir yozadi. Quyidagi blok shuni ko'rsatadi:
// @ts-options: {"experimentalDecorators": true, "emitDecoratorMetadata": true}
import "reflect-metadata";
const Injectable = (): ClassDecorator => () => {};
type Holat = "yangi" | "bajarildi";
@Injectable()
class Hisobot {
constructor(
readonly tz: string,
readonly limit: number,
readonly holat: Holat,
readonly kun: Date,
readonly teglar: string[],
) {}
}
const turlar: unknown[] =
Reflect.getMetadata("design:paramtypes", Hisobot);
console.log(turlar.map((t) => (t as { name: string }).name));Konsolda:
[ 'String', 'Number', 'String', 'Date', 'Array' ]Nima bo'ldi? Kompilyator har turni JavaScript'da mavjud eng yaqin konstruktor bilan almashtirdi. string — String, number — Number. Holat birlashmasi — ham String, chunki uning hamma qiymati satr. string[] — shunchaki Array: ichidagi tur yo'qoldi.
Bu Nest uchun nimani anglatadi? String — klass, demak Nest uni token deb qidiradi. Demo loyihada constructor(readonly tz: string) li xizmatni ishga tushirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Xabarning boshi:
Nest can't resolve dependencies of the HisobotService (?). Please make sure that the argument String at index [0] is available in the HisobotModule module.Bu safar argumentning nomi bor — String. Lekin hech kim String ni provider qilib ro'yxatdan o'tkazmaydi. Xabar "String provider'ini qo'shing" deyayotgandek tuyuladi, aslida esa sabab boshqa: satr sozlamani constructor'ga tur bilan so'rab bo'lmaydi. Yechim interfeysdagidek — token: @Inject(ILOVA_SOZLAMALARI) sozlamalar: IlovaSozlamalari. vazifalar-api dagi tz aynan shu yo'l bilan keladi.
Tekshirib ko'ring:
constructor(readonly ombor: VazifaOmbori, readonly limit: number).VazifaOmbori— interfeys. Nest xabarida argumentlar qanday ko'rinadi va qaysi biri birinchi bo'lib xato beradi?
Javob
Demo loyihada tekshirdik. Xabar: dependencies of the A (?, Number) va the argument at index [0] — nomsiz. Interfeys Object ga aylangan, Nest uni "noma'lum" deydi. Nest argumentlarni chapdan o'ngga yechadi va birinchi topilmaganida to'xtaydi, shuning uchun xabar index [0] haqida. Birinchisini @Inject(VAZIFA_OMBORI) bilan tuzatdik — keyingi ishga tushishda xabar argument Number at index [1] ga o'tdi. Ikkalasini bir yo'la tuzating: ikkala parametrga ham token.
4. Injection token
4.1 Uch xil token
Injection token (bog'liqlik kaliti) — konteynerdagi provider'ning nomi. Konteyner "nima kerak?" degan savolga shu kalit bo'yicha javob qidiradi.
| Token | Misol | Qachon |
|---|---|---|
| klass | VazifalarService |
provider — klass; tur va token bir xil |
Symbol |
VAZIFA_OMBORI |
interfeys, oddiy qiymat, sozlama |
| satr | "OMBOR" |
kichik loyiha, tez sinov |
Klass tokeni eng qulay — hech narsa yozmaysiz, Nest turdan oladi. Interfeys va oddiy qiymat uchun esa token o'ylab topiladi.
Tokenni do'kondagi tovar shtrix-kodiga o'xshatish mumkin. Sotuvchi tovarni ko'rinishiga qarab emas, shtrix-kodi bo'yicha topadi. Bir xil ko'rinishdagi ikki tovarning kodi har xil bo'lishi mumkin — tizim ularni adashtirmaydi. Symbol esa har chaqiruvda yangi shtrix-kod beradi.
4.2 Nega Symbol, satr emas
Satr token (@Inject("CONFIG")) ham ishlaydi. Lekin ikki kutubxona bir xil "CONFIG" ni ishlatsa, ular bir-birining provider'ini ustidan yozadi — xato jim chiqadi. Symbol("CONFIG") esa har chaqiruvda yangi, takrorlanmas qiymat (Symbol). Uni faqat eksport qilingan o'zgaruvchi orqali olish mumkin — tasodifiy to'qnashuv yo'q.
Symbol("…") ichidagi satr — faqat tavsif. U xato xabarlarida ko'rinadi: Provider'lar darsidagi Symbol(VAZIFA_OMBORI) shu. Tavsifni token nomiga teng yozing — xabar o'qilishi oson bo'ladi.
vazifalar-api da hamma token bitta faylda — tokenlar.ts. Bitta joy: qaysi tokenlar borligini ko'rish uchun bitta faylni ochish yetadi.
Diqqat: Token qiymati bilan uning turi alohida yashaydi.
@Inject(LOG) private readonly log: Logda Nest faqatLOGga qaraydi. AgarLOGostida boshqa narsa ro'yxatdan o'tgan bo'lsa (masalan, xato bilan sozlama obyekti), TypeScript buni ushlamaydi. Token va tur juftligini bitta faylda, yonma-yon izoh bilan saqlang.
5. Nest'ning DI xatolari
Kanon uch xil tipik xatoni ataylab chiqardi. Xabarlar qatorlari bo'yicha qisqartirilgan (Nest bo'sh qatorlarni ham beradi).
5.1 Provider ro'yxatda yo'q
Xizmat constructor'ida Soat bor, lekin modulning providers ida Soat yo'q. Xabarning birinchi qatoridagi uch narsaga qarang: qavs ichidagi (?), argument nomi va modul nomi:
2) Soat provider sifatida ro'yxatda yo'q: XATO
Nest can't resolve dependencies of the Xizmat (?). Please make sure that the argument Soat at index [0] is available in the ProviderYoq module.
Potential solutions:
- Is ProviderYoq a valid NestJS module?
- If Soat is a provider, is it part of the current ProviderYoq?
- If Soat is exported from a separate @Module, is that module imported within ProviderYoq?
@Module({
imports: [ /* the Module containing Soat */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errorsModullar va Provider'lar darslarida ko'rgan xabar. Bu safar uni "konteyner tili" bilan o'qiymiz: konteyner design:paramtypes dan Soat ni oldi, uni joriy modul ko'rinishida qidirdi va topmadi. Soat klass bo'lgani uchun xabarda nomi aniq.
5.2 Interfeys token'siz — chalg'ituvchi xabar
InterfeysliXizmat ning constructor'ida readonly ombor: Ombor — interfeys, @Inject siz. Bu xabar uzun, lekin uning ikki joyi muhim: birinchi qatordagi argument nomi va «Potential solutions» ro'yxati:
3) interfeys turi token'siz (Object): XATO
Nest can't resolve dependencies of the InterfeysliXizmat (?). Please make sure that the argument at index [0] is available in the current module.
Potential solutions:
- The dependency at index [0] appears to be undefined at runtime
- This commonly occurs when using 'import type' instead of 'import' for injectable classes
- Check your imports and change:
❌ import type { SomeService } from './some.service';
✅ import { SomeService } from './some.service';
- Ensure the imported class is decorated with @Injectable() or is a valid provider
- If using dynamic imports, ensure the class is available at runtime, not just for type checking
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errorsTarjimasi: "0-indeksdagi bog'liqlik ish vaqtida undefined ko'rinadi. Bu ko'pincha injectable klasslar uchun import o'rniga import type ishlatilganda yuz beradi."
Birinchi qatorga qarang: the argument at index [0] — argument nomi yo'q (Soat yoki Symbol(OMBOR) kabi). Maslahatlar esa butunlay boshqa narsa haqida: import type va @Injectable(). Bizning holatda ikkalasi ham to'g'ri — sabab interfeys. Metama'lumotda Object turibdi, Nest esa uni "noma'lum" deb hisobladi.
Shuning uchun bu xabar chalg'itadi: siz importlarni tekshirib, hech narsa topmaysiz. O'qish qoidasi: xabarda argument nomi yo'q bo'lsa — avval constructor'dagi o'sha indeksga qarang. U interfeys yoki type bo'lsa — @Inject(TOKEN) qo'shing. Klass bo'lsa — import type emasligini tekshiring.
5.3 Token bor, provider yo'q
4) @Inject(OMBOR) bor, lekin OMBOR provider'i yo'q: XATO
Nest can't resolve dependencies of the TokenliXizmat (?, YOQ). Please make sure that the argument Symbol(OMBOR) at index [0] is available in the TokenProviderYoq module.Qolgan maslahatlar «Provider ro'yxatda yo'q» bo'limidagidek. Endi argument nomi — Symbol(OMBOR). Demak token bor, lekin modulda bunday provider yo'q. (?, YOQ) — ikkinchi argument @Optional() @Inject("YOQ"); u ham yo'q, lekin ixtiyoriy bo'lgani uchun xato faqat birinchisida. Provider'lar darsidagi Symbol(VAZIFA_OMBORI) xatosi aynan shu tur edi.
flowchart TB
A["constructor parametri i"] --> B{"self:paramtypes da token bormi?"}
B -- ha --> C["token bo'yicha qidir"]
B -- yo'q --> D{"design:paramtypes turi"}
D -- klass --> E["klass bo'yicha qidir"]
D -- "Object / undefined" --> F["xato: argument nomsiz"]
C --> G{"modulda bormi?"}
E --> G
G -- ha --> H["nusxa beriladi"]
G -- yo'q --> I["xato: nomi bilan"]Diagramma xatolarning qayerdan chiqishini ko'rsatadi. Pastdagi «xato: nomi bilan» — provider ro'yxatda yo'q holatlar (klass ham, token ham). «Xato: argument nomsiz» — interfeys tokensiz qolgan holat.
Tekshirib ko'ring:
Nest can't resolve dependencies of the EksportService (Symbol(VAZIFA_OMBORI), ?)— qaysi argument muammo va qayerdan izlaysiz?
Javob
Ikkinchi (index [1]) argument. Birinchisi — VAZIFA_OMBORI — topildi. EksportService constructor'ida ikkinchi parametr — @Inject(LOG) log. Demak LOG provider'i ko'rinmayapti: global modul ulanmagan yoki exports da LOG yo'q. Xabarning qolgan qismi (argument … at index [1] is available in the EksportModule) aniq tokenni aytadi.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
the argument at index [N] (nomsiz) |
interfeys/tur tokensiz yoki import type klass |
@Inject(TOKEN) yoki oddiy import |
argument Symbol(X) at index [N] |
token bor, provider ro'yxatda yo'q | { provide: X, … } qo'shing |
| Xizmat ishlaydi, lekin noto'g'ri obyekt keladi | ikki joyda bir xil satr token | Symbol token |
| Testda soxta ombor berib bo'lmaydi | klass bog'liqligini o'zi new qiladi |
constructor orqali oling |
design:paramtypes umuman yo'q |
emitDecoratorMetadata o'chiq yoki dekorator yo'q |
tsconfig va @Injectable() |
argument String at index [N] |
constructor'da string (yoki number) turi |
sozlama uchun token va @Inject |
7. Mashqlar
Mashqlar kurs/mashqlar/30/08-di-konteyner/ da.
1-mashq (oson): metama'lumotni toping
constructor(soat: Soat, @Inject(LOG) log: Log, sozlama: { tz: string }) uchun:
design:paramtypesdagi birinchi element —- Ikkinchi element (
Log— pino turi) — - Uchinchi element (obyekt turi) —
- Qaysi argument Nest'da ishlamaydi va xabarda nomsiz chiqadi?
index []
Yechim
- Soat — klass, ish vaqtida mavjud.
- Object — tur ish vaqtida yo'q; lekin
@Inject(LOG)uni token bilan almashtiradi, shuning uchun bu argument ishlaydi. - Object — obyekt turi; token yo'q.
- 2 — uchinchi argument: tokensiz
Object. Yechim —@Inject(ILOVA_SOZLAMALARI),vazifalar-apidagidek.
2-mashq (o'rta): qattiq bog'lanishni yechish
Quyidagi klass bog'liqligini o'zi yaratadi:
class Bildirishnoma {
#sms = new HaqiqiySms(); // tarmoqqa yuboradi
yubor(tel, matn) {
return this.#sms.jonat(tel, `[IlmHamroh] ${matn}`);
}
}Uni qayta yozing: SMS jo'natuvchi constructor'dan kelsin. Soxta jo'natuvchi ({ jonat: (tel, m) => ... }) yuborilgan xabarlarni massivga yozsin. Ikki xabar yuborib, massivni chiqaring. Ishora: klass sintaksisi, push.
Yechim
class Bildirishnoma {
constructor(sms) {
this.sms = sms;
}
yubor(tel, matn) {
return this.sms.jonat(tel, `[IlmHamroh] ${matn}`);
}
}
const yuborilgan = [];
const soxtaSms = {
jonat: (tel, m) => {
yuborilgan.push(`${tel}: ${m}`);
return true;
},
};
const b = new Bildirishnoma(soxtaSms);
b.yubor("+998 90 000 00 00", "Vazifa bajarildi");
b.yubor("+998 90 000 00 01", "Yangi izoh");
console.log(yuborilgan);Konsolda:
[
'+998 90 000 00 00: [IlmHamroh] Vazifa bajarildi',
'+998 90 000 00 01: [IlmHamroh] Yangi izoh'
]Endi Bildirishnoma qaysi SMS provayderi ishlatilishini bilmaydi — faqat jonat metodi borligini. Testda soxta, prodakshnda haqiqiy (29-qismdagi Eskiz yoki PlayMobile moslagichi). Nest'da sms parametri oldiga @Inject(SMS) qo'yilardi — SMS jo'natuvchi interfeys bo'lgani uchun.
3-mashq (qiyin): satr token to'qnashuvi
Kichik konteyner modelida (Map: token → qiymat) ikki "kutubxona" provider qo'shadi. Birinchi kutubxona "CONFIG" satr tokeni ostida { tz: "Asia/Tashkent" } ni, ikkinchisi xuddi shu satr ostida { dsn: "…" } ni ro'yxatdan o'tkazadi. Birinchi kutubxonaning xizmati "CONFIG" ni olib, tz ni chiqarsin — nima chiqadi? Keyin ikkala kutubxonani Symbol("CONFIG") bilan qayta yozing va natijani solishtiring. Ishora: Map.set bir xil kalitni ustidan yozadi (Map); har Symbol(...) — yangi kalit.
Yechim
function konteyner() {
const royxat = new Map();
return {
qosh: (token, qiymat) => royxat.set(token, qiymat),
ol: (token) => royxat.get(token),
soni: () => royxat.size,
};
}
const k1 = konteyner();
k1.qosh("CONFIG", { tz: "Asia/Tashkent" }); // 1-kutubxona
k1.qosh("CONFIG", { dsn: "https://example.com" }); // 2-kutubxona
console.log("satr:", k1.ol("CONFIG").tz, k1.soni());
const K1_CONFIG = Symbol("CONFIG");
const K2_CONFIG = Symbol("CONFIG");
const k2 = konteyner();
k2.qosh(K1_CONFIG, { tz: "Asia/Tashkent" });
k2.qosh(K2_CONFIG, { dsn: "https://example.com" });
console.log("Symbol:", k2.ol(K1_CONFIG).tz, k2.soni());
console.log(K1_CONFIG === K2_CONFIG, String(K1_CONFIG));Konsolda:
satr: undefined 1
Symbol: Asia/Tashkent 2
false Symbol(CONFIG)Satr tokenda ikkinchi kutubxona birinchisining sozlamasini jim ustidan yozdi: konteynerda bitta yozuv qoldi va tz — undefined. Hech qanday xato yo'q. Symbol bilan ikkala yozuv saqlandi, garchi tavsiflari bir xil bo'lsa ham (false — ular har xil qiymat). Haqiqiy Nest'da ham bitta modul ichida bir xil token ostidagi ikki provider'dan oxirgisi qoladi.
8. Loyiha qadami: CONFIG tokeni
8.1 Reja
Bu dars — holatning birinchi qismi. Keyingi darslarda ombor Nest ichida, config bo'yicha yaratiladi (hozir u main.ts da, Nest'dan oldin). Buning uchun fabrikaga to'liq config kerak bo'ladi. Bu darsda config uchun token yaratamiz va uni konteynerga beramiz.
8.2 tokenlar.ts
// tokenlar.ts — DI tokenlari: interfeys (VazifaOmbori) va oddiy
// qiymatlar (log, sozlamalar) uchun. TS interfeysi ish vaqtida YO'Q —
// Nest uni constructor turidan taniy olmaydi, shuning uchun
// Symbol token + @Inject(TOKEN)
export const VAZIFA_OMBORI = Symbol("VAZIFA_OMBORI");
export const LOG = Symbol("LOG");
export const XABARCHI = Symbol("XABARCHI");
export const ILOVA_SOZLAMALARI = Symbol("ILOVA_SOZLAMALARI");
/** to'liq, tekshirilgan Config (keyinroq @nestjs/config bilan
* almashadi) */
export const CONFIG = Symbol("CONFIG");
/** Controller'larga kerakli kichik sozlamalar */
export type IlovaSozlamalari = { tz: string };Ikki o'zgarish. Birinchisi: CONFIG qo'shildi — configYukla qaytargan to'liq, tekshirilgan obyekt uchun (Env validatsiyasi va tipli config obyekti). Ikkinchisi: IDEMPOTENTLIK_OMBORI o'chdi: Controller'lar darsida zaxira uchun yozilgan edi, lekin hech qayerda ishlatilmadi (idempotentlik ombori hozircha middleware'ga to'g'ridan beriladi). Ishlatilmagan token — keyingi o'quvchi uchun yolg'on iz: "bu qayerda provider?". Eksport qilingan bo'lgani uchun linter uni ushlamaydi — qo'lda tozalaymiz.
8.3 bogliqliklar.module.ts
// bogliqliklar.module.ts — tashqarida yaratilgan obyektlar (ombor,
// log, xabarchi, config) DI konteyneriga beriladi: useValue
// provider'lar. @Global — har modul import qilmasdan ishlatadi;
// forRoot(...) — dinamik modul (keyinroq batafsil)
import { Global, Module } from "@nestjs/common";
import type { DynamicModule, Provider } from "@nestjs/common";
import {
CONFIG, ILOVA_SOZLAMALARI, LOG, VAZIFA_OMBORI, XABARCHI,
} from "./tokenlar.ts";
import type { IlovaSozlamalari } from "./tokenlar.ts";
import type { Config } from "./config.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
import type { Log } from "./umumiy/log.ts";
import type { Xabarchi } from "./umumiy/webhook.ts";
export type TashqiBogliqliklar = {
ombor: VazifaOmbori;
log: Log;
xabarchi?: Xabarchi | undefined;
sozlamalar: IlovaSozlamalari;
config?: Config | undefined;
};
@Global()
@Module({})
export class BogliqliklarModule {
static forRoot(
{ ombor, log, xabarchi, sozlamalar, config }: TashqiBogliqliklar,
): DynamicModule {
const providers: Provider[] = [
{ provide: VAZIFA_OMBORI, useValue: ombor },
{ provide: LOG, useValue: log },
// xabarchi ixtiyoriy: yo'q bo'lsa null (xizmat @Optional bilan
// oladi)
{ provide: XABARCHI, useValue: xabarchi ?? null },
{ provide: ILOVA_SOZLAMALARI, useValue: sozlamalar },
...(config === undefined
? []
: [{ provide: CONFIG, useValue: config }]),
];
return {
module: BogliqliklarModule,
providers,
exports: providers.map(
(p) => (p as { provide: symbol }).provide,
),
};
}
}config ixtiyoriy: testlar uni bermaydi (ularga faqat ombor kerak), shuning uchun CONFIG provider'i faqat config bo'lsa qo'shiladi. ...(shart ? [] : [x]) — massivga shartli element qo'shishning keng tarqalgan usuli (spread).
Bu shartli element tur xulosasini buzadi: TypeScript providers ni endi bir xil shakldagi obyektlar massivi deb ololmaydi. Shuning uchun Provider[] — Nest'ning provider turi — aniq yozildi. Provider ichida provide maydoni yo'q turlar ham bor (oddiy klass ham provider), shuning uchun exports da (p as { provide: symbol }) — "bu massivda faqat provide li obyektlar bor, men bilaman" degan tasdiq. Tasdiq (as) xavfli vosita (Type assertion) — u bu yerda adolatli, chunki massivni bir necha qator yuqorida o'zimiz yozdik.
8.4 ilova.ts va main.ts
ilova.ts — Bogliqliklar turiga yangi maydon va AppModule.forRoot ga uzatish:
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
+import type { Config } from "./config.ts";
export type Bogliqliklar = {
ombor: VazifaOmbori;
+ /** to'liq config (main.ts) — DI ichida CONFIG tokeni */
+ config?: Config; export async function ilovaYarat({
ombor,
+ config,
corsOriginlar = [],- AppModule.forRoot({ ombor, log, xabarchi, sozlamalar: { tz } }),
+ AppModule.forRoot({
+ ombor, config, log, xabarchi, sozlamalar: { tz },
+ }),main.ts — bogliqliklar ga bitta qator: config, (ombor: saqlash.ombor, dan keyin). AppModule.forRoot o'zgarmaydi: u TashqiBogliqliklar ni to'liq BogliqliklarModule.forRoot ga uzatadi.
8.5 Tekshiruv
npm run tip
npm run lint
npm testBiz kanon nusxasida yurgizdik (bazasiz): tip 0, lint 0, Vitest 71 o'tdi, 5 fayl SKIP (Postgres va Redis kerak). Xulq o'zgarmadi: CONFIG hali hech kim tomonidan so'ralmaydi — u keyingi darsdagi fabrika uchun tayyorlandi. Hali commit qilmang — bu holatning commit'i Reflector va DiscoveryService darsida.
9. Real ishda
- Test yozishda DI'ning foydasi darrov ko'rinadi:
Test.createTestingModule({ providers: [VazifalarService, { provide: VAZIFA_OMBORI, useValue: soxta }] })— haqiqiy baza kerak emas (NestJS unit testlari). - Katta jamoalarda tokenlar bitta faylda yoki modul bilan yonma-yon (
ombor.tokens.ts) saqlanadi. Satr tokenlar — kod review'da odatda rad etiladi. - "Chalg'ituvchi xabar" (
argument at index [0], nomsiz) — Nest forumlarida eng ko'p so'raladigan xatolardan biri. Uning sababi deyarli doim interfeys yokiimport type. - Sozlamalar: Nest ekotizimida satr va son sozlamalari constructor'ga
stringturi bilan emas, klass orqali keladi:ConfigService— oddiy klass, u token bo'la oladi va undanconfig.get("PORT")bilan o'qiladi. Bu yo'lni @nestjs/config darsida ko'ramiz. Ikkala usulda ham g'oya bitta: ish vaqtida mavjud bo'lmagan tur — token emas. - Intervyu: "DI va IoC farqi nima?", "Nest interfeys turidagi bog'liqlikni qanday topadi?", "
emitDecoratorMetadatanima qiladi?", "NegaSymboltoken?".
Xulosa
- Qattiq bog'lanish testni va almashtirishni qiyinlashtiradi; DI — bog'liqlikni tashqaridan berish, IoC — buni freymvorkka topshirish.
- Nest bog'liqliklarni
design:paramtypesdan o'qiydi; uniemitDecoratorMetadatava dekorator yozadi. - Interfeys va tur ish vaqtida
Object— ular uchun injection token va@Inject(TOKEN);self:paramtypestokenni turdan ustun qo'yadi. stringvanumbermetama'lumotdaStringvaNumberbo'ladi — Nest ularni provider deb qidiradi; sozlama uchun ham token kerak.Symboltoken to'qnashmaydi; satr token jim ustidan yozilishi mumkin.- Xabarda argument nomi bo'lmasa — interfeys yoki
import type; nomi bo'lsa — provider ro'yxatda yo'q. vazifalar-apigaCONFIGtokeni qo'shildi — keyingi darsdagi fabrika uchun.
Keyingi dars: Custom provider'lar — useValue, useClass, useFactory + inject, useExisting va async fabrika; omborni Nest ichida, config bo'yicha yaratadigan provider'larni yozamiz.
Manbalar
- NestJS hujjati — Providers va Custom providers (tokenlar,
@Inject): https://docs.nestjs.com/providers , https://docs.nestjs.com/fundamentals/custom-providers - NestJS hujjati — Common errors ("Cannot resolve dependency"): https://docs.nestjs.com/faq/common-errors
- TypeScript hujjati — Decorators,
emitDecoratorMetadata: https://www.typescriptlang.org/docs/handbook/decorators.html#metadata - Martin Fowler, "Inversion of Control Containers and the Dependency Injection pattern" (2004): https://martinfowler.com/articles/injection.html
- Kanon tajribasi (IlmHamroh, 2026-10-27): metama'lumot va uch DI xatosi, NestJS 12.2.0, Vitest muhitida. Mini konteyner (
ts) bloki — tsc 7.0.2 + reflect-metadata 0.2.2 da tekshirildi; loyiha qadami — 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!