IlmHamroh
JavaScript Full-stack/30-qism. NestJS5/65-dars27 daqiqa
Mundarija (35)

NestJS provider va xizmatlari: @Injectable, constructor injection va singleton

Qisqacha: Provider — Nest o'zi yaratib, boshqa klasslarga bera oladigan obyekt. Eng ko'p uchraydigan turi — xizmat (service): biznes qoidalari yashaydigan @Injectable() klass. Uni modulning providers ro'yxatiga yozasiz, keyin controller constructor'ida so'raysiz: constructor(private readonly xizmat: VazifalarService). Nest nusxani o'zi yaratadi va sukut bo'yicha butun ilovada bitta nusxa ishlatadi (singleton). Interfeys turidagi bog'liqlik uchun token kerak: @Inject(VAZIFA_OMBORI).

Bu darsda

  • @Injectable() nima qilishini va u unutilsa nima bo'lishini haqiqiy xato bilan ko'rasiz.
  • Constructor orqali bog'liqlik olishni (private readonly, @Inject, @Optional) yoza olasiz.
  • Provider sukut bo'yicha singleton ekanini o'lchab ko'rasiz va bu nega muhimligini tushuntirasiz.
  • Qachon klass-xizmat, qachon oddiy yordamchi funksiya kerakligini farqlaysiz.
  • vazifalar-api ning 25-qismdagi vazifaXizmati fabrikasini VazifalarService klassiga ko'chirasiz.

Oldin bilishingiz kerak: Controller'lar va route dekoratorlari, Modullar, express.Router va qatlamli loyiha tuzilmasi, Klasslar TypeScript'da, Legacy dekoratorlar va reflect-metadata.

O'tgan darsda controller'lar yozildi, lekin npm run tip to'rtta xato berdi: VazifalarService va EksportService hali yo'q edi. Bu darsda ularni yozamiz. Controller'lar HTTP bilan ishlaydi, xizmatlar esa "nima qilish kerak" degan savolga javob beradi.

1. Nega bu kerak?

25-qismda xizmat fabrika funksiya edi. ilova.ts uni qo'lda yaratib, routerga berardi:

ts
const xizmat = vazifaXizmati(ombor, () => joriyLog(log), xabarchi);
v1.use("/vazifalar", vazifalarRouteri(xizmat, { tz }));
v1.use("/teglar", teglarRouteri(xizmat));

Bu kodda ikki narsa yaxshi: xizmat HTTP'ni bilmaydi va omborni tashqaridan oladi. Ikkita narsa esa qo'lda: kim kimni yaratishi va yaratish tartibi. Ombor xizmatdan oldin, xizmat routerdan oldin bo'lishi kerak. Bitta router ikkita xizmatni so'rasa, ilova.ts ga yana bir qator qo'shasiz.

Uch xizmatda bu muammo emas. O'ttizta xizmatda esa ilova.ts "kim kimga kerak" degan ulkan ro'yxatga aylanadi. Har yangi bog'liqlik bu ro'yxatni o'zgartiradi.

Nest bu ishni o'z zimmasiga oladi. Siz xizmatga yorliq yopishtirasiz va constructor'da "menga nima kerak"ligini yozasiz. Qolganini — kimni qachon yaratish, kimga berish — Nest qiladi.

O'xshatish: oshxonadagi ta'minot bo'limi. Oshpaz (controller) "menga go'sht va guruch kerak" deb buyurtma yozadi. Ta'minotchi (Nest) omborni biladi, mahsulotni olib keladi va hamma oshpazga bitta ombordan beradi. Oshpaz bozorga o'zi bormaydi.

2. Provider nima

2.1 Ta'rif

Provider (ta'minlovchi) — Nest konteyneri yarata oladigan va boshqa klassga bera oladigan har qanday narsa. Bu so'z bilan NestJS nima va nega darsida tanishgansiz. Provider turlari:

Tur Misol Qaysi darsda
Klass (@Injectable) VazifalarService shu dars
Tayyor qiymat (useValue) log, sozlamalar shu dars, Custom provider'lar
Fabrika (useFactory) baza ulanishi Custom provider'lar
Taxallus (useExisting) bitta obyektga ikki nom Custom provider'lar

Bu darsda asosan birinchi tur — klass. U eng ko'p uchraydi. Ikkinchisini loyihada ham ishlatamiz, chunki vazifalar-api da ombor va log Nest'dan oldin yaratiladi.

2.2 @Injectable()

ts
@Injectable()
export class VazifalarService {
  constructor(
    @Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
  ) {}
}

@Injectable() — "bu klassni Nest yarata oladi, uning constructor'iga qara" degan yorliq. Uning eng muhim ishi ko'rinmas: TypeScript emitDecoratorMetadata sozlamasi bilan dekoratorli klassning constructor turlarini JavaScript'ga yozib qo'yadi (design:paramtypes). Dekorator yo'q klass uchun bu yozuv paydo bo'lmaydi. Buni legacy dekoratorlar darsida va NestJS nima va nega darsida __metadata("design:paramtypes", [AppService]) qatori bilan ko'rgansiz.

2.3 providers ro'yxati

Yorliqning o'zi yetmaydi. Klass modulning providers iga yozilishi kerak:

ts
@Module({
  controllers: [VazifalarController, TeglarController],
  providers: [VazifalarService],
  exports: [VazifalarService],
})
export class VazifalarModule {}

providers — "shu modul nimani yaratadi". exports — "tashqariga nimani beradi" (Modullar darsidagi qoida). Ikkala controller bitta modulda — ular xizmatni import qilmasdan oladi.

Tekshirib ko'ring: @Injectable() bor, lekin klass hech bir modulning providers ida yo'q. Controller uni constructor'da so'rasa nima bo'ladi?

Javob

Ilova ishga tushmaydi: Nest can't resolve dependencies of the …Controller (?). Yorliq "yarata olaman" deydi, providers esa "yarat" deydi. Ikkinchisi bo'lmasa, konteyner bu klassni bilmaydi. Haqiqiy chiqishni «Xatolar: provider yo'q va yorliq yo'q» bo'limida ko'ramiz.

3. Constructor orqali bog'liqlik

3.1 private readonly — qisqa yozuv

ts
constructor(private readonly xizmat: VazifalarService) {}

Bu — TypeScript'ning parameter property yozuvi (Klasslar TypeScript'da). U ikki ishni bir qatorda qiladi: parametrni oladi va this.xizmat maydoniga yozadi. Uzun shakli:

ts
readonly #xizmat: VazifalarService;
constructor(xizmat: VazifalarService) {
  this.#xizmat = xizmat;
}

Nest kodida deyarli har doim qisqa shakl yoziladi. private — maydon klass tashqarisidan ko'rinmasin. readonly — konstruktordan keyin uni almashtirib bo'lmasin. Bog'liqlik bir marta beriladi va o'zgarmaydi — readonly shu niyatni kodda ko'rsatadi.

3.2 Klass turi — tokenning o'zi

private readonly xizmat: VazifalarService da VazifalarService ikki rol o'ynaydi. TypeScript uchun — tur. Nest uchun — token: "konteynerdan shu kalit bilan narsani ber". Klass ish vaqtida ham mavjud (u JavaScript funksiyasi), shuning uchun metama'lumotga yoziladi va Nest uni o'qiy oladi.

3.3 Interfeys — @Inject(TOKEN)

VazifaOmbori — interfeys. Ish vaqtida u yo'q: TypeScript uni o'chirib tashlaydi. Metama'lumotda o'rniga Object qoladi, Nest esa Object dan nima yaratishni bilmaydi. Shuning uchun token qo'lda beriladi:

ts
constructor(
  @Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
  @Inject(LOG) private readonly log: Log,
) {}

VAZIFA_OMBORI — Symbol, o'tgan darsda yozilgan tokenlar.ts da. @Inject(VAZIFA_OMBORI) — "bu parametr uchun shu kalitga qara". Tur (: VazifaOmbori) esa faqat TypeScript uchun qoladi — kodni tekshirish va avtomatik to'ldirish uchun. Interfeys tokensiz ishlatilsa Nest qanday chalg'ituvchi xato berishini DI va IoC konteyner darsida haqiqiy matni bilan ko'ramiz.

3.4 @Optional() — bo'lmasa ham bo'ladi

vazifalar-api da webhook xabarchisi ixtiyoriy: WEBHOOK_URL berilmasa, u yo'q. 25-qismda bu xabarchi?: Xabarchi parametri edi. Nest'da:

ts
@Optional() @Inject(XABARCHI)
private readonly xabarchi: Xabarchi | null = null,

@Optional() — "bu provider topilmasa, xato berma, undefined ber". Kanon kodida biroz boshqacha yo'l ham bor: provider har doim ro'yxatda, lekin qiymati null bo'lishi mumkin (useValue: xabarchi ?? null). Shunda xizmat ichida tekshiruv bitta: this.xabarchi !== null. @Optional() esa token umuman ro'yxatdan tushib qolsa ham ilovani yiqitmaydi — ikki qavatli himoya.

Tekshirib ko'ring: Nega @Inject(LOG) private readonly log: Log da @Inject kerak, private readonly xizmat: VazifalarService da esa kerak emas?

Javob

VazifalarService — klass, u ish vaqtida mavjud va metama'lumotga o'zi yoziladi. Log esa TypeScript turi (pino logger turi) — ish vaqtida yo'q, metama'lumotda Object qoladi. Nest Object bo'yicha hech narsa topa olmaydi, shuning uchun aniq token — LOG — kerak.

4. Singleton: bitta nusxa

4.1 Haqiqiy tajriba

Nest provider'ni sukut bo'yicha butun ilova uchun bir marta yaratadi. Buni o'z demo loyihamizda tekshirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Hisoblagich xizmati har chaqiruvda sonni oshiradi, ikki controller uni ishlatadi:

ts
@Injectable()
class HisoblagichService {
  #soni = 0;
  constructor() {
    yaratildi++;
  }
  oshir(): number {
    return ++this.#soni;
  }
}

@Controller('a')
class AController {
  constructor(private readonly h: HisoblagichService) {}
  @Get() ol() {
    return { a: this.h.oshir() };
  }
}
// BController — xuddi shunday, { b: … } qaytaradi

To'rtta so'rov navbat bilan /a va /b ga:

text
1) singleton: OK
  GET /a → 200 {"a":1}
  GET /b → 200 {"b":2}
  GET /a → 200 {"a":3}
  GET /b → 200 {"b":4}
  HisoblagichService yaratildi: 1 marta

Sonlar 1, 2, 3, 4 — uzilmasdan o'sdi. Ikki controller bitta hisoblagichni ishlatdi. Konstruktor bir marta chaqirildi — ilova ishga tushganda, birinchi so'rovdan oldin.

flowchart LR
  A["AController"] --> H["HisoblagichService (1 nusxa)"]
  B["BController"] --> H
  H --> S["#soni: 1, 2, 3, 4"]

Diagrammadagi ikki strelka bitta qutiga keladi — bu singleton. NestJS nima va nega darsidagi JS konteyner ham aynan shunday ishlagandi.

4.2 Nega bu muhim

EksportService ichida eksport ishlari Map da saqlanadi:

ts
@Injectable()
export class EksportService {
  readonly #ishlar = new Map<string, Ish>();
  // …
}

POST /v1/eksportlar ishni Map ga yozadi, GET /v1/eksportlar/:id uni o'qiydi. Ikkalasi bitta nusxadan foydalangani uchun ishlaydi. Agar har so'rov yangi EksportService olsa, GET bo'sh Map ni ko'rardi va "topilmadi" derdi.

Singleton'ning ikkinchi tomoni: nusxa hamma so'rovlar uchun umumiy. Unga so'rovga xos ma'lumot (joriy foydalanuvchi, sorovId) yozmang — ikki parallel so'rov bir-birining qiymatini ustidan yozadi. vazifalar-api da so'rov konteksti AsyncLocalStorage orqali olinadi: joriyLog(this.log) har chaqiruvda joriy so'rovning logger'ini beradi. Har so'rovga alohida nusxa ham mumkin — Provider scope'lari darsida, narxi bilan.

Diqqat: Singleton xizmatda this.joriyFoydalanuvchi = … kabi maydon — klassik xato. Bitta so'rovda yozilgan qiymatni boshqa so'rov o'qiydi. Bu xato testda deyarli ko'rinmaydi (testda so'rovlar ketma-ket), prodakshnda esa yuk ostida chiqadi.

5. JS modelida @Injectable yorlig'i

Nest ichida @Injectable nima qilishini kichik modelda takrorlaymiz. Bu blok brauzerda ishlaydi. Dekorator o'rniga oddiy funksiya: injectable(Klass, kerak) metama'lumot jadvaliga "bu klassga nima kerak" deb yozadi. Konteyner faqat shu jadvalga qaraydi:

js
const metama = new Map(); // klass → kerakli klasslar

function injectable(Klass, kerak) {
  metama.set(Klass, kerak);
}

function yarat(Klass, tayyor = new Map()) {
  if (tayyor.has(Klass)) return tayyor.get(Klass);
  // yorliq yo'q — "bog'liqligi yo'q" deb hisoblanadi
  const kerak = metama.get(Klass) ?? [];
  const nusxa = new Klass(...kerak.map((K) => yarat(K, tayyor)));
  tayyor.set(Klass, nusxa);
  return nusxa;
}

class SoatXizmati {
  hozir() { return "2026-11-02T09:00:00Z"; }
}
class HisobotXizmati {
  constructor(soat) { this.soat = soat; }
  sarlavha() { return `Hisobot (${this.soat.hozir()})`; }
}

injectable(SoatXizmati, []);
injectable(HisobotXizmati, [SoatXizmati]);
console.log(yarat(HisobotXizmati).sarlavha());

metama.delete(HisobotXizmati); // yorliqni "unutdik"
try {
  console.log(yarat(HisobotXizmati).sarlavha());
} catch (e) {
  console.log(`${e.name}: ${e.message}`);
}

Konsolda:

text
Hisobot (2026-11-02T09:00:00Z)
TypeError: Cannot read properties of undefined (reading 'hozir')

Ikkinchi qatorga qarang. Yorliq yo'q bo'lganda konteyner xato bermadi — klassni argumentsiz yaratdi. this.soat — undefined, xato esa faqat metod chaqirilganda chiqdi. Haqiqiy Nest ham aynan shunday qiladi — keyingi bo'limda ko'ramiz.

Model haqiqiy Nest'dan soddaroq: unda modul chegarasi, tokenlar va @Optional yo'q. Haqiqiy Nest metama'lumotni Reflect.getMetadata("design:paramtypes", Klass) bilan o'qiydi — bizning metama.get(Klass) ning o'rni.

6. Xatolar: provider yo'q va yorliq yo'q

6.1 providers da yo'q

Demo loyihada AController ni hisoblagichsiz modulga qo'ydik:

ts
@Module({ controllers: [AController] })
class ProvidersizModule {}
text
Nest can't resolve dependencies of the AController (?). Please make sure that the argument HisoblagichService at index [0] is available in the ProvidersizModule module.

Potential solutions:
- Is ProvidersizModule a valid NestJS module?
- If HisoblagichService is a provider, is it part of the current ProvidersizModule?
- If HisoblagichService is exported from a separate @Module, is that module imported within ProvidersizModule?
  @Module({
    imports: [ /* the Module containing HisoblagichService */ ]
  })

For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors

Tarjimasi: "Nest AController ning bog'liqliklarini topa olmadi (?). ProvidersizModule modulida 0-indeksdagi HisoblagichService argumenti mavjudligiga ishonch hosil qiling." Xabarni Modullar darsidagidek o'qing: (?) — topilmagan argument, index [0] — constructor'dagi o'rni, in the ProvidersizModule module — qayerda qidirilgani. Ikkinchi maslahat aynan bizning holat: "HisoblagichService provider bo'lsa, u joriy modulning qismimi?" Yo'q — providers: [HisoblagichService] qo'shilsa, xato yo'qoladi.

Bu xato yaxshi xato: ilova ishga tushayotganda chiqadi va birinchi so'rovgacha yetmaydi.

6.2 @Injectable() unutilgan

Endi xavfliroq holat. DekoratorsizService ga SoatService kerak, lekin @Injectable() yozilmagan:

ts
// @Injectable() yo'q — lekin constructor'da bog'liqlik bor
class DekoratorsizService {
  constructor(private readonly soat: SoatService) {}
  sarlavha(): string {
    return `Hisobot (${this.soat.hozir()})`;
  }
}

@Module({
  controllers: [CController],
  providers: [SoatService, DekoratorsizService],
})
class DekoratorsizModule {}

Natija (logda jarayon raqami va vaqtni olib tashladik, iz qatorlari qisqartirildi):

text
3) @Injectable() unutilgan: OK
ERROR [ExceptionsHandler] TypeError: Cannot read properties of undefined (reading 'hozir')
    at DekoratorsizService.sarlavha (file:///…/dist/t-xizmat.js:78:38)
    at CController.ol (file:///…/dist/t-xizmat.js:87:28)
  GET /c → 500 {"statusCode":500,"message":"Internal server error"}

Ilova ishga tushdi (OK). Klass providers da bor, Nest uni yaratdi — lekin metama'lumot bo'lmagani uchun argumentsiz. Xato faqat so'rov kelganda chiqdi: this.soat — undefined. Tarjimasi: "undefined ning xususiyatlarini o'qib bo'lmaydi (hozir o'qilmoqda)". Xabar SoatService haqida bir og'iz ham demaydi — siz undefined qayerdan kelganini o'zingiz qidirasiz. JS modelidagi ikkinchi qator aynan shu edi.

Diqqat: Constructor'ida bog'liqligi bor har provider klassiga @Injectable() yozing. Bog'liqligi yo'q klass yorliqsiz ham ishlaydi — shuning uchun xato kech, birinchi bog'liqlik qo'shilganda chiqadi.

7. Xizmat va yordamchi funksiya

Har bir funksiyani provider qilish shart emas. vazifalar-api da ikkalasi ham bor:

Xizmat (provider) Yordamchi (oddiy funksiya)
VazifalarService asosYol(req, toplam)
EksportService csvQator(qiymatlar, ajratgich)
ombor (VAZIFA_OMBORI) kursorYoz, proyeksiya

Farqni qanday topish mumkin? Uchta savol:

  1. Holat bormi? EksportService da #ishlar Map'i — holat. Holatli narsa provider bo'ladi: kim uni yaratishi va nechta nusxa bo'lishi muhim.
  2. Tashqi bog'liqlik bormi? Ombor, log, tarmoq — testda ularni almashtirish kerak bo'ladi. Provider almashtiriladi, oddiy import qilingan funksiya — yo'q.
  3. Natija faqat argumentlarga bog'liqmi? asosYol — kirish satri, chiqish satri. Unda ombor ham, log ham yo'q. Bunday toza funksiya oddiy export function bo'lib qoladi va to'g'ridan import qilinadi.

Uchala savolga "yo'q" bo'lsa — yordamchi funksiya. Uni provider qilsangiz, faqat dekorator va modul yozuvi qo'shiladi, foyda yo'q. O'tgan darsda asosYol brauzerda sinalgan edi — u Nest'ni umuman bilmaydi.

Tekshirib ko'ring: narxniFormatla(tiyin) funksiyasi Intl.NumberFormat("uz-Latn-UZ") bilan son qaytaradi. Uni provider qilish kerakmi?

Javob

Kerak emas. Holati yo'q, tashqi bog'liqligi yo'q, natija faqat argumentga bog'liq — toza funksiya. Uni testda almashtirishga ehtiyoj ham yo'q: haqiqiy funksiya tez va deterministik. Agar u valyuta kursini tarmoqdan olsa edi — boshqa gap: tarmoq bog'liqligi paydo bo'ladi va xizmat qilish mantiqli.

8. Fabrika funksiyadan klassga

Ko'chishning asosiy qismi — 25-qismdagi xizmat.ts. Mantiq aynan qoladi, faqat shakl o'zgaradi:

25–27 (xizmat.ts) Nest (vazifalar.service.ts)
export function vazifaXizmati(ombor, log, xabarchi) @Injectable() export class VazifalarService
parametrlar constructor(@Inject(...) private readonly ...)
ombor.ol(id) this.ombor.ol(id)
log().info(...) joriyLog(this.log).info(...)
xabarchi?: Xabarchi, !== undefined @Optional(), Xabarchi | null, !== null
return { royxat, ol, yarat, … } klass metodlari
ilova.ts da vazifaXizmati(…) providers: [VazifalarService]

Ikki kichik farq bor. Birinchisi — log. 25-qismda xizmat log ni funksiya sifatida olardi (() => joriyLog(log)), har chaqiruvda joriy so'rov logger'ini olish uchun. Endi xizmat ildiz logger'ni oladi va joriyLog(this.log) ni o'zi chaqiradi — natija bir xil. Ikkinchisi — xabarchi bo'lmasa undefined emas, null. Sababi «@Optional() — bo'lmasa ham bo'ladi» bo'limida.

Qolgan hamma narsa — MAKS_VAZIFA = 1000, 404 va 412 xatolari, faqat false → true o'tishida webhook — so'zma-so'z ko'chdi. Shuning uchun 25–27 dagi testlar xizmatning xulqini o'zgarishsiz tekshira oladi.

flowchart TB
  VC["VazifalarController"] --> VS["VazifalarService"]
  TC["TeglarController"] --> VS
  EC["EksportController"] --> ES["EksportService"]
  VS -->|"@Inject(VAZIFA_OMBORI)"| O["ombor"]
  VS -->|"@Inject(LOG)"| L["log"]
  VS -.->|"@Optional XABARCHI"| X["xabarchi yoki null"]
  ES --> O
  ES --> L

Diagrammada tokenli bog'liqliklar strelka yorlig'ida. Punktir — ixtiyoriy bog'liqlik. Ombor va log ikki xizmatda bir xil — ular ham singleton.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Nest can't resolve dependencies of the X (?) klass providers da yo'q yoki eksport qilinmagan providers ga qo'shing yoki egasidan exports
Ilova ko'tariladi, so'rovda Cannot read properties of undefined @Injectable() unutilgan har bog'liqlikli provider'ga yorliq
… the argument at index [0] … (nomsiz) interfeys turi tokensiz @Inject(TOKEN)
Ikki so'rov bir-birining ma'lumotini ko'radi singleton'da so'rovga xos maydon AsyncLocalStorage yoki parametr
Testda soxta ombor ishlamaydi xizmat new PgOmbori() ni o'zi yaratadi bog'liqlikni constructor'dan oling

Oxirgi qator 25-qismdagi qoidaning davomi: obyekt o'ziga kerak narsani o'zi new bilan yaratsa, uni tashqaridan almashtirib bo'lmaydi. Nest bu qoidani majburiy qilmaydi — new yozish taqiqlanmagan — lekin Nest'ning test vositalari (keyinroq NestJS unit testlari) constructor'dan kelgan bog'liqliklarni almashtirishga qurilgan.

10. Mashqlar

Mashqlar kurs/mashqlar/30/05-provider-xizmat/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Klassni Nest yarata oladigan qiluvchi dekorator — @()
  • Interfeys turidagi bog'liqlik uchun parametrga yoziladigan dekorator — @(TOKEN)
  • Topilmasa xato bermaydigan bog'liqlik — @()
  • Provider sukut bo'yicha butun ilovada nechta nusxada? —
  • asosYol(req, "vazifalar") provider bo'lishi kerakmi (ha/yo'q)? —
Yechim
  • Injectable — metama'lumot yozilishi uchun yorliq.
  • Inject — @Inject(VAZIFA_OMBORI); interfeys ish vaqtida yo'q.
  • Optional — topilmasa undefined beriladi.
  • 1 — singleton; demo tajribada yaratildi: 1 marta.
  • yo'q — toza funksiya: holat ham, tashqi bog'liqlik ham yo'q.

2-mashq (o'rta): fabrikadan klassga

25-qism uslubidagi fabrika funksiya berilgan:

js
function narxXizmati(kurs) {
  return {
    somda: (usd) => Math.round(usd * kurs.ol()),
  };
}

Uni NarxXizmati klassiga aylantiring: bog'liqlik constructor'dan kelsin, somda(usd) metod bo'lsin. Soxta kurs obyekti ({ ol: () => 12_650 }) bilan somda(10) va somda(2.5) ni chiqaring. Keyin boshqa soxta kurs (12_700) bilan yangi nusxa yarating — natija o'zgarishi kerak. Ishora: klass sintaksisi, this.kurs.

Yechim
js
class NarxXizmati {
  constructor(kurs) {
    this.kurs = kurs;
  }
  somda(usd) {
    return Math.round(usd * this.kurs.ol());
  }
}

const soxtaKurs = { ol: () => 12_650 };
const x = new NarxXizmati(soxtaKurs);
console.log(x.somda(10));
console.log(x.somda(2.5));

const boshqa = new NarxXizmati({ ol: () => 12_700 });
console.log(boshqa.somda(10));

Konsolda:

text
126500
31625
127000

Mantiq aynan bir xil, faqat bog'liqlik endi this.kurs da. Nest'da kurs oldiga @Inject(KURS) qo'yilardi (kurs manbai interfeys bo'lgani uchun) va klassga @Injectable(). Testda esa xuddi shu yerdagidek soxta kurs beriladi — kod o'zgarmaydi.

3-mashq (qiyin): modelga @Optional qo'shing

Darsdagi JS modelida kerak ro'yxatiga obyekt ham yozilishi mumkin bo'lsin: { klass: Xabarchi, ixtiyoriy: true }. Bunday bog'liqlik ro'yxatda (metama da) bo'lmasa, konteyner xato bermasin — undefined bersin. Oddiy (ixtiyoriy bo'lmagan) bog'liqlik ro'yxatda bo'lmasa esa Error tashlasin: "<Klass> ning <i>-argumenti (<Bog'liqlik>) ro'yxatda yo'q". Sinov: Xizmat ga Ombor (majburiy) va Xabarchi (ixtiyoriy) kerak. Ishora: kerak.map((k, i) => …), typeof k === "function" — oddiy klass, aks holda obyekt.

Yechim
js
const metama = new Map();
function injectable(Klass, kerak) {
  metama.set(Klass, kerak);
}

function yarat(Klass, tayyor = new Map()) {
  if (tayyor.has(Klass)) return tayyor.get(Klass);
  const kerak = metama.get(Klass) ?? [];
  const argumentlar = kerak.map((k, i) => {
    const B = typeof k === "function" ? k : k.klass;
    const ixtiyoriy = typeof k === "function" ? false : k.ixtiyoriy;
    if (!metama.has(B)) {
      if (ixtiyoriy) return undefined;
      throw new Error(
        `${Klass.name} ning ${i}-argumenti (${B.name}) ` +
          "ro'yxatda yo'q",
      );
    }
    return yarat(B, tayyor);
  });
  const nusxa = new Klass(...argumentlar);
  tayyor.set(Klass, nusxa);
  return nusxa;
}

class Ombor {}
class Xabarchi {}
class Xizmat {
  constructor(ombor, xabarchi) {
    this.ombor = ombor;
    this.xabarchi = xabarchi;
  }
}

injectable(Xizmat, [Ombor, { klass: Xabarchi, ixtiyoriy: true }]);
try {
  yarat(Xizmat);
} catch (e) {
  console.log(e.message);
}
injectable(Ombor, []);
const x = yarat(Xizmat);
console.log(x.ombor instanceof Ombor, x.xabarchi);
injectable(Xabarchi, []);
console.log(yarat(Xizmat).xabarchi instanceof Xabarchi);

Konsolda:

text
Xizmat ning 0-argumenti (Ombor) ro'yxatda yo'q
true undefined
true

Birinchi urinish — Ombor majburiy va ro'yxatda yo'q, xato. Ikkinchisida Ombor bor, Xabarchi yo'q — undefined, xato yo'q. Uchinchisi yangi tayyor jadvali bilan boshlandi va Xabarchi ni ham topdi. Nest'ning @Optional() i ham shunday: provider bo'lmasa — undefined. Kanon xizmatidagi = null sukut qiymati shu undefined ni null ga aylantiradi.

11. Loyiha qadami: xizmatlar

11.1 Qaysi fayllar

Bu dars — butun v1 API'ni Nest'ga ko'chirishning uchinchi qismi. Yangi fayllar:

Fayl Nima
src/vazifalar/vazifalar.service.ts 25-qism xizmat.ts mantig'i, @Injectable klass
src/eksport/eksport.service.ts eksport.ts dagi ishlar va fon bajarilishi
src/bogliqliklar.module.ts ombor, log, xabarchi, sozlamalar — useValue

O'zgaradigan: vazifalar.module.ts, eksport.module.ts — providers va exports. Eski xizmat.ts, eksport.ts hali joyida — Express API ular bilan ishlashda davom etadi. Kanon fayllarida qatorlar uzun edi — telefon uchun bo'ldik (izohlar qisqartirildi, bitta joyda takror ifoda const joriy ga olindi; ma'no aynan). Bo'lingan nusxani biz kanon nusxasida tsc --noEmit va ESLint bilan tekshirdik.

11.2 vazifalar.service.ts

ts
// vazifalar.service.ts — biznes qoidalari (25-qism xizmat.ts dan;
// mantiq o'zgarmadi). Endi factory funksiya emas — @Injectable klass:
// bog'liqliklarni Nest konteyneri constructor orqali beradi (ombor —
// token bilan, chunki VazifaOmbori — interfeys)
import { Inject, Injectable, Optional } from "@nestjs/common";
import {
  KonfliktXatosi, ShartXatosi, TopilmadiXatosi,
} from "../umumiy/xatolar.ts";
import { joriyLog } from "../umumiy/log.ts";
import type { Log } from "../umumiy/log.ts";
import type { Xabarchi } from "../umumiy/webhook.ts";
import { LOG, VAZIFA_OMBORI, XABARCHI } from "../tokenlar.ts";
import type {
  AlmashtirishVazifasi, RoyxatNatija, RoyxatSorovi, Shart, TegSoni,
  VazifaOmbori, YozishNatijasi,
} from "./ombor.ts";
import type {
  ToliqVazifa, Vazifa, VazifaOzgarishi, YangiVazifa,
} from "./vazifa.ts";

export const MAKS_VAZIFA = 1000;
const topilmadi = (id: number) => new TopilmadiXatosi(
  `Vazifa ${String(id)} topilmadi`, "vazifa-topilmadi",
  "Vazifa topilmadi",
);

/** Ombor natijasini qiymatga yoki xatoga aylantiradi:
 * topilmadi → 404, versiya mos emas → 412 */
function natija<T>(id: number, n: YozishNatijasi<T>): T {
  if (n.tur === "topilmadi") throw topilmadi(id);
  if (n.tur === "konflikt") {
    const joriy = String(n.joriy.versiya);
    throw new ShartXatosi(
      `Vazifa ${String(id)} boshqa so'rov bilan o'zgargan ` +
        `(joriy versiya ${joriy}) — qayta o'qing`,
      `"${joriy}"`,
    );
  }
  return n.qiymat;
}

@Injectable()
export class VazifalarService {
  constructor(
    @Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
    @Inject(LOG) private readonly log: Log,
    // vazifa bajarilganda tashqi xabar (webhook); provider qiymati
    // null bo'lsa — yuborilmaydi
    @Optional() @Inject(XABARCHI)
    private readonly xabarchi: Xabarchi | null = null,
  ) {}

  royxat(sorov: RoyxatSorovi): Promise<RoyxatNatija> {
    return this.ombor.royxat(sorov);
  }

  teglar(): Promise<TegSoni[]> {
    return this.ombor.teglar();
  }

  async ol(id: number): Promise<Vazifa> {
    const v = await this.ombor.ol(id);
    if (!v) throw topilmadi(id);
    return v;
  }

  async yarat(yangi: YangiVazifa): Promise<Vazifa> {
    if ((await this.ombor.soni({})) >= MAKS_VAZIFA) {
      throw new KonfliktXatosi(
        `Ro'yxat to'lgan (${String(MAKS_VAZIFA)})`,
        { kod: "royxat-toldi", sarlavha: "Vazifalar soni chegarada" },
      );
    }
    const v = await this.ombor.yarat(yangi);
    // joriyLog — JORIY so'rov logger'i (AsyncLocalStorage):
    // sorovId avtomatik qo'shiladi
    joriyLog(this.log).info({ vazifaId: v.id }, "vazifa yaratildi");
    return v;
  }

  async yangila(
    id: number, ozgarish: VazifaOzgarishi, shart?: Shart,
  ): Promise<Vazifa> {
    // Faqat false → true o'tishida xabar: avvalgi holatni bilish
    // kerak
    const avval =
      this.xabarchi !== null && ozgarish.bajarildi === true
        ? await this.ombor.ol(id)
        : undefined;
    const v = natija(
      id, await this.ombor.yangila(id, ozgarish, shart),
    );
    if (
      this.xabarchi !== null && avval?.bajarildi === false &&
      v.bajarildi
    ) {
      this.xabarchi("vazifa.bajarildi", v);
    }
    return v;
  }

  /** PUT: butun resurs (matn va bajarildi) almashtiriladi —
   * idempotent */
  almashtir(
    id: number, toliq: ToliqVazifa, shart?: Shart,
  ): Promise<Vazifa> {
    return this.yangila(id, toliq, shart);
  }

  /** PUT /v1/vazifalar — butun to'plam (frontend "almashtir") */
  almashtirHammasi(
    vazifalar: readonly AlmashtirishVazifasi[],
  ): Promise<{ vazifalar: Vazifa[]; versiya: number }> {
    return this.ombor.almashtirHammasi(vazifalar);
  }

  /** Bulk yaratish: yaroqlilari yaratiladi, qolganlari — xato bilan
   * (qisman muvaffaqiyat) */
  async toplamYarat(
    yangilar: readonly YangiVazifa[],
  ): Promise<Vazifa[]> {
    const soni = await this.ombor.soni({});
    if (soni + yangilar.length > MAKS_VAZIFA) {
      throw new KonfliktXatosi(
        `Ro'yxat to'ladi (${String(MAKS_VAZIFA)})`,
        { kod: "royxat-toldi", sarlavha: "Vazifalar soni chegarada" },
      );
    }
    const yaratilgan: Vazifa[] = [];
    for (const y of yangilar) {
      yaratilgan.push(await this.ombor.yarat(y));
    }
    return yaratilgan;
  }

  async ochir(id: number, shart?: Shart): Promise<void> {
    natija(id, await this.ombor.ochir(id, shart));
    joriyLog(this.log).info({ vazifaId: id }, "vazifa o'chirildi");
  }
}

Klassda HTTP haqida bitta so'z ham yo'q: na req, na res, na status kod. Xatolar — 25-qismdagi xato klasslari (Xato turlari); ularni HTTP javobiga aylantirish filtrning ishi (keyingi dars). toplamYarat dagi for … await — ketma-ket yaratish, 25–27 dagidek. Bu yerda yashiringan eski xato bor, uni keyinroq Tranzaksiyalar NestJS'da darsida topamiz.

11.3 eksport.service.ts

ts
// eksport.service.ts — uzoq davom etuvchi operatsiya (25-qism
// eksport.ts mantiqi): ishlar xotirada (bitta jarayon), fonda
// bajariladi. Provider sukut bo'yicha SINGLETON — `ishlar` Map'i
// hamma so'rovlar uchun bitta
import { randomUUID } from "node:crypto";
import { setImmediate as navbat } from "node:timers/promises";
import { Inject, Injectable } from "@nestjs/common";
import { IlovaXatosi } from "../umumiy/xatolar.ts";
import { csvQator } from "../umumiy/csv.ts";
import { joriyLog } from "../umumiy/log.ts";
import type { Log } from "../umumiy/log.ts";
import { LOG, VAZIFA_OMBORI } from "../tokenlar.ts";
import type {
  KursorQiymati, VazifaOmbori,
} from "../vazifalar/ombor.ts";
import type { EksportSorovi } from "../vazifalar/sxema.ts";
import type { Vazifa } from "../vazifalar/vazifa.ts";

export type EksportHolati =
  "kutilmoqda" | "bajarilmoqda" | "tayyor" | "xato";
export type Ish = {
  id: string;
  sorov: EksportSorovi;
  holat: EksportHolati;
  yaratildi: string;
  tayyor: number;
  jami: number;
  natija?: string;
  xato?: string;
  tugash: number;
};

const USTUNLAR = [
  "id", "matn", "bajarildi", "teglar", "yaratildi", "yangilandi",
] as const;
const SAQLASH_MS = 60 * 60 * 1000; // natija 1 soat saqlanadi
const BOLAK = 100;

/** discriminatedUnion natijasi: sorov.format bo'yicha TS har tarmoqda
 * to'g'ri turni biladi (ajratgich faqat csv'da) */
function yoz(
  sorov: EksportSorovi,
  vazifalar: readonly Vazifa[],
  birinchi: boolean,
): string {
  if (sorov.format === "json") {
    return vazifalar.map(
      (v, i) => (birinchi && i === 0 ? "" : ",") + JSON.stringify(v),
    ).join("");
  }
  return vazifalar.map((v) => csvQator(
    [v.id, v.matn, v.bajarildi, v.teglar.join(" "), v.yaratildi,
      v.yangilandi],
    sorov.ajratgich,
  )).join("");
}

@Injectable()
export class EksportService {
  readonly #ishlar = new Map<string, Ish>();
  /** tikuvlar (seam): testda soxta soat */
  soat: () => number = Date.now;

  constructor(
    @Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
    @Inject(LOG) private readonly log: Log,
  ) {}

  boshla(sorov: EksportSorovi): Ish {
    const ish: Ish = {
      id: randomUUID(), sorov, holat: "kutilmoqda",
      yaratildi: new Date(this.soat()).toISOString(),
      tayyor: 0, jami: 0, tugash: this.soat() + SAQLASH_MS,
    };
    this.#ishlar.set(ish.id, ish);
    // Javob kutmaydi: ish fonda; xatosi ish holatiga yoziladi (yutib
    // yuborilmaydi). Fon ishi ham shu so'rov konteksti (sorovId)
    // bilan loglanadi
    const l = joriyLog(this.log);
    this.#bajar(ish).then(
      () => {
        l.info(
          { eksportId: ish.id, soni: ish.tayyor }, "eksport tayyor",
        );
      },
      (x: unknown) => {
        ish.holat = "xato";
        ish.xato = "Eksport bajarilmadi";
        l.error({ err: x, eksportId: ish.id }, "eksport bajarilmadi");
      },
    );
    return ish;
  }

  async #bajar(ish: Ish): Promise<void> {
    ish.holat = "bajarilmoqda";
    ish.jami = await this.ombor.soni({});
    let chiqish = ish.sorov.format === "json"
      ? "["
      : csvQator(USTUNLAR, ish.sorov.ajratgich);
    let keyin: KursorQiymati | undefined;
    for (;;) {
      const n = await this.ombor.royxat({
        filtr: {}, saralash: [], limit: BOLAK,
        sahifa: keyin === undefined
          ? { tur: "kursor" }
          : { tur: "kursor", keyin },
      });
      chiqish += yoz(ish.sorov, n.vazifalar, ish.tayyor === 0);
      ish.tayyor += n.vazifalar.length;
      if (!n.borKeyingi || n.keyingi === undefined) break;
      keyin = n.keyingi;
      // event loop'ni bo'shatamiz — boshqa so'rovlar kutib qolmasin
      await navbat();
    }
    ish.natija =
      ish.sorov.format === "json" ? chiqish + "]" : chiqish;
    ish.holat = "tayyor";
    ish.tugash = this.soat() + SAQLASH_MS;
  }

  topilsin(id: string): Ish {
    for (const [k, i] of this.#ishlar) {
      if (i.tugash <= this.soat()) this.#ishlar.delete(k);
    }
    const ish = this.#ishlar.get(id);
    if (ish === undefined) {
      throw new IlovaXatosi(
        404, `Eksport ${id} topilmadi (yoki muddati o'tgan)`,
        { kod: "eksport-topilmadi", sarlavha: "Eksport topilmadi" },
      );
    }
    return ish;
  }

  korinish(ish: Ish, asos: string): Record<string, unknown> {
    const k: Record<string, unknown> = {
      id: ish.id, format: ish.sorov.format, holat: ish.holat,
      yaratildi: ish.yaratildi, tayyor: ish.tayyor, jami: ish.jami,
    };
    if (ish.holat === "tayyor") k.fayl = `${asos}/${ish.id}/fayl`;
    if (ish.xato !== undefined) k.xato = ish.xato;
    return k;
  }
}

25-qismdagi eksport.ts ikki ishni qilardi: routerni yasardi va ishlarni boshqarardi. Endi HTTP qismi — o'tgan darsdagi EksportController da, ishlar — shu xizmatda. Xizmatdagi #ishlar va #bajar — private maydon va metodlar: ularni klass tashqarisidan hech kim o'zgartira olmaydi. soat esa ataylab ochiq — testda soxta vaqt berish uchun tikuv (seam, Mock, stub, spy va fake).

11.4 bogliqliklar.module.ts

Ombor, log va xabarchi main.ts da Nest'dan oldin yaratiladi — 27-qismdagi omborYarat o'zgarmaydi. Ularni konteynerga berish uchun useValue provider'lar:

ts
// bogliqliklar.module.ts — tashqarida yaratilgan obyektlar (ombor,
// log, xabarchi) 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 } from "@nestjs/common";
import {
  ILOVA_SOZLAMALARI, LOG, VAZIFA_OMBORI, XABARCHI,
} from "./tokenlar.ts";
import type { IlovaSozlamalari } from "./tokenlar.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;
};

@Global()
@Module({})
export class BogliqliklarModule {
  static forRoot(
    { ombor, log, xabarchi, sozlamalar }: TashqiBogliqliklar,
  ): DynamicModule {
    const providers = [
      { 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 },
    ];
    return {
      module: BogliqliklarModule,
      providers,
      exports: providers.map((p) => p.provide),
    };
  }
}

Uch yangi narsa, uchalasi keyingi darslarda chuqur:

  • { provide: VAZIFA_OMBORI, useValue: ombor } — "shu token so'ralsa, mana bu tayyor obyektni ber". Nest uni yaratmaydi, faqat beradi (Custom provider'lar).
  • @Global() — Modullar darsidagi global modul: xizmatlar BogliqliklarModule ni import qilmasdan oladi.
  • static forRoot(...) — modulni qiymatlar bilan qaytaradi. @Module({}) bo'sh: tarkib ish vaqtida beriladi (Dynamic modullar).

exports: providers.map((p) => p.provide) — hamma tokenni eksport qiladi; ro'yxatni ikki marta yozmaslik uchun.

11.5 Modullar

src/vazifalar/vazifalar.module.ts:

ts
// vazifalar.module.ts — vazifalar xususiyat moduli: controller'lar +
// xizmat. Ombor, log — global BogliqliklarModule dan (token bilan)
import { Module } from "@nestjs/common";
import { VazifalarController } from "./vazifalar.controller.ts";
import { TeglarController } from "./teglar.controller.ts";
import { VazifalarService } from "./vazifalar.service.ts";

@Module({
  controllers: [VazifalarController, TeglarController],
  providers: [VazifalarService],
  // boshqa modul (masalan eksport) xizmatdan foydalanmoqchi bo'lsa —
  // eksport qilinadi
  exports: [VazifalarService],
})
export class VazifalarModule {}

src/eksport/eksport.module.ts:

ts
// eksport.module.ts — eksport xususiyat moduli
import { Module } from "@nestjs/common";
import { EksportController } from "./eksport.controller.ts";
import { EksportService } from "./eksport.service.ts";

@Module({
  controllers: [EksportController],
  providers: [EksportService],
})
export class EksportModule {}

BogliqliklarModule hali hech qayerga ulanmagan. Uni AppModule ga ulash main.ts ning o'zgarishini talab qiladi — keyingi darsda.

11.6 Tekshiruv: bitta kutilgan xato

bash
npm run tip
npm run lint
npm test

Biz shu holatni kanon nusxasida tekshirdik (Node 24.21.0, NestJS 12.2.0). tip — 0 xato: o'tgan darsdagi to'rt TS2307/TS7006 yo'qoldi. lint — 0. npm test esa (Vitest hozircha faqat src/**/*.spec.ts ni yurgizadi — 2-darsdagi salom.controller.spec.ts) yiqiladi:

text
 FAIL  src/salom.controller.spec.ts [ src/salom.controller.spec.ts ]
Error: Nest can't resolve dependencies of the VazifalarService (?, Symbol(LOG), Symbol(XABARCHI)). Please make sure that the argument Symbol(VAZIFA_OMBORI) at index [0] is available in the VazifalarModule module.

Potential solutions:
- Is VazifalarModule a valid NestJS module?
- If Symbol(VAZIFA_OMBORI) is a provider, is it part of the current VazifalarModule?
- If Symbol(VAZIFA_OMBORI) is exported from a separate @Module, is that module imported within VazifalarModule?
  @Module({
    imports: [ /* the Module containing Symbol(VAZIFA_OMBORI) */ ]
  })

For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors

 Test Files  1 failed (1)
      Tests  2 skipped (2)

Tarjimasi: "Nest VazifalarService ning bog'liqliklarini topa olmadi (?, Symbol(LOG), Symbol(XABARCHI)). VazifalarModule modulida 0-indeksdagi Symbol(VAZIFA_OMBORI) argumenti mavjudligiga ishonch hosil qiling."

Xabarni o'qishni mashq qilamiz:

  • Qavs ichida uchta argument: ? — topilmagani, keyingi ikkitasi — tokenlar. Tokenli argument klass nomi emas, Symbol(...) bo'lib ko'rinadi — Symbol("LOG") dagi tavsif shu yerda kerak bo'ldi.
  • Xabar faqat birinchi topilmaganini aytadi. LOG ham hozir yo'q, lekin Nest 0-argumentda to'xtadi.
  • in the VazifalarModule module — xizmat shu modulda yaratilmoqda va token na shu modulda, na global modullarda yo'q. Sabab bizga ma'lum: BogliqliklarModule hali daraxtga ulanmagan.

Test AppModule ni ko'taradi, AppModule esa VazifalarModule ni. Faqat shu xato bo'lsa — hammasi to'g'ri. Boshqa xato chiqsa (masalan, tip da TS2305 yoki Nest xabarida boshqa klass nomi), uni hozir tuzating.

Express API bu paytda ishlashda davom etadi: biz npm run start:express bilan yurgizib, POST /v1/vazifalar yubordik — 201 Created. Nest qismi esa bootstrap'ni kutyapti. Hali commit qilmang — commit Bootstrap: main.ts darsida, butun API ko'chgandan keyin.

12. Real ishda

  • Har Nest loyihasida *.service.ts — eng ko'p fayl. Jamoa qoidasi odatda: controller yupqa, xizmat qalin; xizmat HTTP'ni bilmaydi.
  • Kod review'da tez-tez uchraydigan izohlar: "bu klassda @Injectable() yo'q", "xizmatda new bilan ombor yaratilmasin", "singleton'da so'rov holati saqlanmasin".
  • Yordamchilar papkasi (utils/, umumiy/) — toza funksiyalar uchun. Ularni provider qilish ham, static metodli klassga yig'ish ham shart emas.
  • Intervyu: "Provider nima?", "Nest provider'ining sukut scope'i qaysi?" (singleton), "@Injectable() nima uchun kerak?", "Interfeysni qanday inject qilasiz?" (token + @Inject).

Xulosa

  • Provider — Nest yaratib beradigan obyekt; xizmat — biznes qoidalari yashaydigan @Injectable() klass.
  • Klass providers da bo'lishi va (kerak bo'lsa) exports qilinishi shart; yorliq yolg'iz yetmaydi.
  • Constructor'da private readonly x: Klass — Nest turidan taniydi; interfeys uchun @Inject(TOKEN), ixtiyoriy uchun @Optional().
  • Provider sukut bo'yicha singleton: bitta nusxa, hamma so'rovga umumiy — so'rov holati unga yozilmaydi.
  • @Injectable() unutilsa ilova ko'tariladi, xato esa so'rovda undefined bo'lib chiqadi.
  • Toza funksiya (holatsiz, tashqi bog'liqliksiz) — oddiy yordamchi bo'lib qoladi.

Keyingi dars: Bootstrap: main.ts va ilova sozlamalari — NestFactory.create, Express middleware'lari, versiyalash va xato filtri; eski Express fayllarini o'chiramiz, testlarni Vitest'ga o'tkazamiz va commit qilamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS provider va xizmatlari: @Injectable, constructor injection va singleton — IlmHamroh