IlmHamroh
JavaScript Full-stack/30-qism. NestJS52/65-dars18 daqiqa
Mundarija (25)

NestJS standalone ilova: createApplicationContext, CLI buyruqlar va monorepo

Qisqacha: Nest ilovasi faqat HTTP server emas. NestFactory.createApplicationContext(AppModule) o'sha modullar, DI, config va lifecycle hook'larni ko'taradi, lekin controller, middleware va port yo'q. Shu bilan terminal buyrug'i, cron skripti yoki fon ishchisi yoziladi: ctx.get(VazifalarService) — tayyor xizmat. Ish tugagach await ctx.close() shart: aks holda pool va taymerlar jarayonni tirik ushlab turadi. Bir repoda bir nechta Nest ilovasi kerak bo'lsa — Nest CLI monorepo (apps/, libs/).

Bu darsda

  • HTTP ilova va standalone kontekst farqini tushuntirasiz.
  • vazifalar-api ga npm run buyruq -- teglar | eksport buyrug'ini qo'shasiz.
  • ctx.close() unutilsa nima bo'lishini haqiqiy tajribada ko'rasiz.
  • Chiqish kodlari (64, 78) va stdout tozaligi qoidasini bilasiz — kanonda topilgan kamchilik bilan.
  • nest g app va nest g lib monorepo tuzilmasini qanday yaratishini o'qiysiz.

Oldin bilishingiz kerak: Node'da CLI dastur anatomiyasi, Commander bilan buyruqlar tizimi, Lifecycle hook'lar, Dynamic modullar, Rejalashtirish: @nestjs/schedule.

O'tgan darsda HTTP qatlamini almashtirib ko'rdik — Express yoki Fastify. Bugun undan ham ko'proq qilamiz: HTTP qatlamini butunlay olib tashlaymiz. Qolgani — modullar, xizmatlar, ombor — o'z joyida.

1. Nega bu kerak?

Har backend'da HTTP'siz ishlar bor:

  • Eksport: "hamma vazifalarni CSV qilib ber" — administrator terminaldan.
  • Bir martalik skript: eski ma'lumotni tuzatish, hisobot chiqarish, test ma'lumotini to'ldirish.
  • Cron: server cron'i har kecha soat 3 da bir buyruqni ishga tushiradi.
  • Fon ishchisi (worker): navbatdan ish olib bajaradigan alohida jarayon.

Bularning hammasiga ilovaning o'sha qismlari kerak: config tekshiruvi, ombor (Postgres pool'i yoki fayl), VazifalarService qoidalari. Ularni qayta yozish — ikki joyda bir xil mantiq degani. Express'da odatda omborYarat(config) ni qo'lda chaqirib, kerakli obyektlarni yig'ib chiqardik.

24-qismda CLI dastur yozishni o'rgandik (Node'da CLI dastur anatomiyasi): argumentlar, chiqish kodlari, stdout/stderr. Endi shu bilimni Nest DI bilan birlashtiramiz.

O'xshatish: restoran oshxonasi. Odatda buyurtma ofitsiant (HTTP) orqali keladi. Lekin ertalab oshpaz zal ochilmasdan oldin xamir tayyorlaydi — o'sha oshxona, o'sha jihozlar, o'sha retseptlar, faqat mijoz yo'q. Standalone kontekst — zal yopiq oshxona.

2. createApplicationContext

2.1 Ikki kirish nuqtasi

flowchart TB
  M["main.ts: NestFactory.create"] --> A["AppModule.forRoot()"]
  B["buyruq.ts: createApplicationContext"] --> A
  A --> P["provider'lar: config, ombor, VazifalarService"]
  A --> L["lifecycle: onModuleInit / onModuleDestroy"]
  M --> H["HTTP: controller, middleware, port"]
  B -. "yo'q" .-> H

Ikki fayl bitta modul daraxtini ishlatadi. Farq faqat pastki o'ng burchakda: main.ts HTTP qatlamini ham quradi, buyruq.ts — yo'q.

NestFactory.create createApplicationContext
Provider'lar, DI bor bor
Config, lifecycle hook'lar bor bor
@Interval, @Cron ishlaydi ishlaydi
Controller, middleware, guard bor yo'q
HTTP server, port bor yo'q
Natija INestApplication INestApplicationContext

@Interval va @Cron kontekstda ham ishlaydi — bu muhim, quyida ko'ramiz. ctx.get(Klass) — kerakli provider'ni beradi, xuddi testdagi m.get(...) kabi (NestJS unit testlari).

2.2 JS modelida

Kichik model (brauzerda ishlaydi). Xizmat ishga tushganda "taymer" ochadi, yopilganda — yopadi:

js
// Bitta modul — ikki kirish nuqtasi: HTTP ilova va HTTP'siz kontekst
const ochiqTaymerlar = new Set();

class EksportXizmati {
  onModuleInit() {
    // @Interval o'xshashi: har daqiqa eski ishlarni tozalash
    this.t = { nomi: "eksport-tozalash" };
    ochiqTaymerlar.add(this.t);
  }
  onModuleDestroy() {
    ochiqTaymerlar.delete(this.t);
  }
  teglar() {
    return [{ teg: "o'qish", soni: 2 }, { teg: "bozor", soni: 1 }];
  }
}

function kontekstYarat({ http }) {
  const xizmat = new EksportXizmati();
  xizmat.onModuleInit();
  return {
    http: http ? "port ochildi" : "HTTP yo'q",
    get: () => xizmat,
    close: () => xizmat.onModuleDestroy(),
  };
}

const ctx = kontekstYarat({ http: false });
console.log(ctx.http, ctx.get().teglar().length, "ta teg");
console.log("close'dan oldin ochiq:", ochiqTaymerlar.size);
ctx.close();
console.log("close'dan keyin ochiq:", ochiqTaymerlar.size);

const unutilgan = kontekstYarat({ http: false });
unutilgan.get().teglar();
console.log("close unutildi — ochiq:", ochiqTaymerlar.size);

Konsolda:

text
HTTP yo'q 2 ta teg
close'dan oldin ochiq: 1
close'dan keyin ochiq: 0
close unutildi — ochiq: 1

Oxirgi qatorga qarang: ish tugadi, lekin taymer ochiq qoldi. Node'da ochiq taymer yoki ulanish bo'lsa — jarayon tugamaydi (Event loop fazalari chuqur). Model soddalashtirilgan: haqiqiy Nest'da onModuleDestroy va onApplicationShutdown hook'lari ctx.close() da, hamma modul bo'ylab chaqiriladi (Lifecycle hook'lar).

Tekshirib ko'ring: Standalone kontekstda ctx.get(VazifalarController) nima qaytaradi? Controller'ning metodini chaqirib, HTTP marshrutini "sinab" bo'ladimi?

Javob

Tekshirdik: nusxa qaytadi — controller: VazifalarController. Nest controller klassini ham yaratadi, chunki u modulda yozilgan. Lekin marshrut, middleware, guard, pipe va filtr yo'q: metodni chaqirsangiz, oddiy funksiya chaqiruvi bo'ladi, HTTP so'rov emas. Bu "marshrut sinovi" emas — validatsiya va xato filtri ishlamaydi. Standalone kontekstdan faqat provider'larni (xizmatlarni) oling. Biznes mantiq xizmatda bo'lishining yana bir sababi shu.

3. vazifalar-api ning buyrug'i

3.1 src/buyruq.ts

Kanon fayli, telefon uchun bo'lingan (ma'no o'zgarmagan, izohlar qisqartirilgan):

ts
// buyruq.ts — standalone ilova (HTTP'siz): o'sha modullar, o'sha DI,
// o'sha config; terminal buyrug'i yoki cron skripti uchun.
//   npm run buyruq -- teglar   — teglar va soni (jadval)
//   npm run buyruq -- eksport [csv|json] > fayl
//     — hamma vazifalar stdout'ga (EksportService mantiqi)
// NestFactory.createApplicationContext — controller'lar, HTTP server,
// middleware YO'Q; provider'lar va lifecycle hook'lar BOR
import "reflect-metadata";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module.ts";
import { XotiraIdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import { VazifalarService } from "./vazifalar/vazifalar.service.ts";
import { csvQator } from "./umumiy/csv.ts";
import { ConfigXatosi } from "./config.ts";

const [buyruq, format = "csv"] = process.argv.slice(2);
if (buyruq !== "teglar" && buyruq !== "eksport") {
  console.error(
    "Foydalanish: npm run buyruq -- teglar | eksport [csv|json]",
  );
  process.exit(64); // EX_USAGE
}

let ctx;
try {
  ctx = await NestFactory.createApplicationContext(
    AppModule.forRoot({
      idempotentlikOmbori: new XotiraIdempotentlikOmbori(),
    }),
    { logger: ["error", "warn"], abortOnError: false },
  );
} catch (x) {
  if (!(x instanceof ConfigXatosi)) throw x;
  console.error(x.message);
  process.exit(78);
}
try {
  const xizmat = ctx.get(VazifalarService);
  if (buyruq === "teglar") {
    const teglar = await xizmat.teglar();
    console.table(teglar.map(({ teg, soni }) => ({ teg, soni })));
  } else {
    // kursor bilan bo'lak-bo'lak (100 tadan) — katta ro'yxat
    // xotiraga birdan yuklanmaydi
    const ustunlar = [
      "id", "matn", "bajarildi", "teglar", "yaratildi", "yangilandi",
    ] as const;
    if (format === "csv") process.stdout.write(csvQator(ustunlar));
    let keyin: (string | number | boolean)[] | undefined;
    let birinchi = true;
    if (format === "json") process.stdout.write("[");
    for (;;) {
      const n = await xizmat.royxat({
        filtr: {},
        saralash: [],
        limit: 100,
        sahifa: keyin === undefined
          ? { tur: "kursor" }
          : { tur: "kursor", keyin },
      });
      for (const v of n.vazifalar) {
        process.stdout.write(
          format === "csv"
            ? csvQator([v.id, v.matn, v.bajarildi,
              v.teglar.join(" "), v.yaratildi, v.yangilandi])
            : (birinchi ? "" : ",") + JSON.stringify(v),
        );
        birinchi = false;
      }
      if (!n.borKeyingi || n.keyingi === undefined) break;
      keyin = n.keyingi;
    }
    if (format === "json") process.stdout.write("]\n");
  }
} finally {
  // @Interval taymerlari va pool — yopilmasa jarayon tugamaydi
  await ctx.close();
}

Qismma-qism:

  • Argumentlar — process.argv.slice(2), 24-qismdagidek (Node'da CLI dastur anatomiyasi). Noma'lum buyruq — foydalanish matni stderr ga va chiqish kodi 64.
  • Kontekst — AppModule.forRoot(...) — HTTP ilovadagi o'sha dinamik modul (Dynamic modullar). idempotentlikOmbori — modul talab qiladigan, lekin buyruqqa kerak bo'lmagan bog'liqlik; xotiradagisi yetadi.
  • logger: ["error", "warn"] — Nest'ning o'z tizim loglari (InstanceLoader…) chiqmasin, faqat xato va ogohlantirish.
  • abortOnError: false — provider fabrikasi yiqilsa (config xato, baza yo'q), Nest process.exit(1) qilmaydi, xato catch ga keladi. Shu yerda ConfigXatosi 78 ga aylanadi.
  • Eksport — kursor bilan 100 tadan (Pagination: offset, cursor, keyset). 10 000 vazifa bo'lsa ham xotirada bir vaqtda 100 tasi turadi. csvQator — 25-qismdagi eksport funksiyasi.
  • finally + ctx.close() — xato bo'lsa ham kontekst yopiladi (try, catch, finally).

3.2 Chiqish kodlari

64 va 78 tasodifiy raqam emas. Ular Unix'dagi sysexits.h kelishuvidan:

Kod Nomi Ma'nosi
0 — muvaffaqiyat
1 — umumiy xato (ushlanmagan istisno)
64 EX_USAGE buyruq noto'g'ri ishlatildi
78 EX_CONFIG sozlama xato

Cron yoki CI skripti kodga qarab qaror qiladi: 64 — dasturchi xatosi (buyruq satri), 78 — muhit xatosi (.env). 24-qismda chiqish kodlari umumiy ma'noda o'tilgandi; bu yerda aniq kelishuvni ishlatamiz.

4. Natija: biz ishga tushirdik

Lokal nusxada ilovani SAQLASH=fayl bilan ko'tarib, curl bilan to'rtta vazifa qo'shdik va bittasini bajarilgan qildik. Keyin serverni to'xtatib, buyruqni yurgizdik (NestJS 12.2.0, Node 24.21.0; sanalar — o'sha paytdagi):

bash
npm run build
SAQLASH=fayl npm run buyruq -- teglar
text
┌─────────┬──────────┬──────┐
│ (index) │ teg      │ soni │
├─────────┼──────────┼──────┤
│ 0       │ "o'qish" │ 2    │
│ 1       │ 'bozor'  │ 1    │
│ 2       │ 'oila'   │ 1    │
└─────────┴──────────┴──────┘

console.table satrlarni qo'shtirnoq bilan chiqaradi: apostrofli satr — qo'sh qo'shtirnoqda, qolgani — bittalikda. Bu Node'ning util.inspect qoidasi, xato emas. Jarayon 1.8 s da tugadi. Kanon Postgres va Prisma bilan o'lchaganda — 2.15 s.

bash
SAQLASH=fayl npm run buyruq -- eksport csv
text
id,matn,bajarildi,teglar,yaratildi,yangilandi
1,Non olish #bozor,true,bozor,2026-11-02T06:30:41.049Z,2026-11-02T06:30:41.350Z
2,Postgres repository yozish #o'qish,false,o'qish,2026-11-02T06:30:41.141Z,2026-11-02T06:30:41.141Z
3,Onamga dori olib borish #oila,false,oila,2026-11-02T06:30:41.215Z,2026-11-02T06:30:41.215Z
4,SQL JOIN mashqlari #o'qish,false,o'qish,2026-11-02T06:30:41.292Z,2026-11-02T06:30:41.292Z
{"daraja":"info","time":"2026-11-02T06:30:54.204Z","ilova":"vazifalar-api","msg":"ombor ulanishlari yopildi"}

Oxirgi qatorga qarang — u CSV emas. Bu haqda «Topilma: stdout iflos» bo'limida.

Noto'g'ri buyruq va noto'g'ri sozlama:

bash
npm run buyruq -- xyz; echo "exit $?"
SAQLASH=postgres npm run buyruq -- teglar; echo "exit $?"
text
Foydalanish: npm run buyruq -- teglar | eksport [csv|json]
exit 64
[Nest] 9344  - 02/11/2026, 11:36:27   ERROR [ExceptionHandler] ConfigXatosi: Sozlama noto'g'ri:
✖ SAQLASH=postgres/prisma uchun DATABASE_URL majburiy
  → at DATABASE_URL
    at configYukla (file:///C:/Users/…/vazifalar-api/dist/config.js:74:15)
… (Nest ichki stack qatorlari)
Sozlama noto'g'ri:
✖ SAQLASH=postgres/prisma uchun DATABASE_URL majburiy
  → at DATABASE_URL
exit 78

Yo'lni qisqartirdik va Nest ichidagi stack qatorlarini olib tashladik. Kod to'g'ri — 78. Lekin xabar ikki marta chiqdi: avval Nest'ning ExceptionHandler logi (stack bilan), keyin bizning console.error. abortOnError: false jarayonni o'ldirmaydi, lekin Nest xatoni baribir logga yozadi. Toza chiqish kerak bo'lsa — logger: false yoki ["warn"]; u holda boshqa xatolar ham yashirinadi. Kanon ikki marta chiqishni qabul qilgan.

5. ctx.close() nega shart

Kanon izohida: "@Interval taymerlari va pool — yopilmasa jarayon tugamaydi". Tekshirdik: buyruq.js nusxasidan await ctx.close() qatorini olib tashlab, 15 soniya chegarasi bilan yurgizdik:

bash
timeout 15 node dist/buyruq-yopiqsiz.js teglar; echo "exit $?"
text
┌─────────┬──────────┬──────┐
│ (index) │ teg      │ soni │
├─────────┼──────────┼──────┤
│ 0       │ "o'qish" │ 2    │
│ 1       │ 'bozor'  │ 1    │
│ 2       │ 'oila'   │ 1    │
└─────────┴──────────┴──────┘
exit 124

Jadval chiqdi — ish bajarildi. Lekin jarayon tugamadi: 15 soniyadan keyin timeout uni o'ldirdi, 124 — timeout ning "vaqt tugadi" kodi. Sabab — EksportService dagi @Interval("eksport-tozalash", 60_000) (Rejalashtirish): har daqiqa eski eksport ishlarini tozalaydigan taymer kontekstda ham ishga tushdi. Postgres rejimida unga pool ulanishlari qo'shiladi.

Cron'da bunday buyruq har kecha yangi "osilgan" jarayon qoldiradi — bir haftadan keyin serverda yettita. process.exit(0) bilan majburan chiqish ham yechim emas: pool yopilmaydi, yozilayotgan fayl oxirigacha yozilmasligi mumkin.

Diqqat: Standalone kontekstni har doim try/finally ichida await ctx.close() bilan yoping. Xato bo'lsa ham.

6. Topilma: stdout iflos

Eksport chiqishining oxirgi qatori — JSON log: "ombor ulanishlari yopildi". Uni OmborModule ning yopish hook'i yozadi (ctx.close() paytida). vazifalar-api ning logi Pino orqali stdout ga yoziladi — 25-qismdagi 12-factor qoidasi (Logging asoslari). HTTP serverda bu to'g'ri. Buyruqda esa stdout — ma'lumot kanali.

Tekshirdik:

bash
npm run buyruq -- eksport csv > vazifalar.csv
tail -1 vazifalar.csv
text
{"daraja":"info","time":"2026-11-02T06:31:10.924Z","ilova":"vazifalar-api","msg":"ombor ulanishlari yopildi"}

CSV faylning oxirgi qatori — log. Excel yoki boshqa dastur uni "noto'g'ri qator" deb ko'radi, JSON eksportda esa fayl umuman JSON bo'lmay qoladi. Kanon o'lchovi bu chiqishni | head -4 bilan ko'rsatgani uchun xato ko'rinmay qolgan.

Qoida (24-qism CLI darsidan): stdout — ma'lumot, stderr — xabarlar va log. Hozircha ishlaydigan yo'l — buyruqni yuqoriroq log darajasi bilan yurgizish:

bash
LOG_LEVEL=warn npm run buyruq -- eksport csv > vazifalar.csv

LOG_LEVEL=warn bilan info loglar chiqmaydi va fayl toza bo'ladi (tekshirdik). To'liq tuzatish — buyruq rejimida logni stderr'ga yo'naltirish. Uni 3-mashqda modelda qilasiz. Bu kamchilik kanonda tuzatilmagan — loyihangizda LOG_LEVEL=warn ni buyruq skriptiga qo'shib qo'yishingiz mumkin.

Tekshirib ko'ring: Nega bu xato HTTP serverda muammo emas, buyruqda esa muammo?

Javob

HTTP serverda ma'lumot tarmoq orqali (javob tanasi) ketadi, stdout faqat log uchun. Buyruqda esa stdout'ning o'zi ma'lumot: > fayl.csv uni faylga yozadi. Logni ham stdout'ga yozsak, ikki oqim aralashadi.

7. Nest CLI monorepo

7.1 nest g app va nest g lib

Bir repoda bir nechta Nest ilovasi kerak bo'lishi mumkin: API va alohida ishchi, yoki API va admin panel backend'i. Ular umumiy kodni (modellar, config, ombor) ulashadi. Nest CLI buni monorepo rejimi deb ataydi.

Sinab ko'rdik: nest new mono, keyin ikki buyruq (biz ishga tushirdik, @nestjs/cli 12.0.9):

bash
npx @nestjs/cli@12.0.9 generate app ishchi
npx @nestjs/cli@12.0.9 generate library umumiy

Birinchi buyruq chiqishi (qisqartirilgan):

text
    Added @rspack/core, fork-ts-checker-webpack-plugin, tsconfig-paths-webpack-plugin and webpack-node-externals for the rspack builder; running "npm install"
DELETE src
DELETE test
CREATE apps/mono/src/app.module.ts (265 bytes)
CREATE apps/mono/src/main.ts (245 bytes)
CREATE apps/ishchi/src/main.ts (254 bytes)
CREATE apps/ishchi/src/ishchi.module.ts (286 bytes)
CREATE apps/ishchi/test/jest-e2e.json (192 bytes)
UPDATE tsconfig.json (775 bytes)
UPDATE package.json (1584 bytes)
UPDATE nest-cli.json (787 bytes)

Uchta muhim narsa:

  1. Loyiha qayta tuzildi: src/ va test/ o'chirildi, eski ilova apps/mono/ ga ko'chdi. Bu buyruq mavjud loyihani o'zgartiradi — avval commit qiling.
  2. Builder almashdi: monorepo uchun CLI rspack builder'ini (webpack'ga mos, tezroq yig'uvchi) qo'ydi va to'rt paket o'rnatdi. nest build ishchi — 2.75 s da yig'ildi (Rspack 2.3.0 compiled successfully).
  3. Shablon qoldiqlari: yangi ilovaga test/jest-e2e.json keldi, lekin loyiha Vitest'da. npm run lint esa eski yo'llarni qidiradi: oxlint --type-aware src/ test/ → "No files found to lint". Ikkalasini qo'lda tuzatish kerak.

Kutubxona buyrug'i libs/umumiy/ yaratdi va tsconfig.json ga yo'l taxallusini qo'shdi:

json
"paths": {
  "@app/umumiy": ["./libs/umumiy/src/index.ts"],
  "@app/umumiy/*": ["./libs/umumiy/src/*"]
}

Endi har ilova import { UmumiyModule } from "@app/umumiy" yozadi. Builder bu taxallusni yig'ish paytida haqiqiy yo'lga aylantiradi.

7.2 nest-cli.json

json
{
  "monorepo": true,
  "root": "apps/mono",
  "projects": {
    "ishchi": {
      "type": "application",
      "root": "apps/ishchi",
      "entryFile": "main",
      "sourceRoot": "apps/ishchi/src"
    }
  }
}

Qisqartirilgan: haqiqiy faylda mono loyihasi, umumiy kutubxonasi va har biriga tsConfigPath ham bor. nest build ishchi, nest start ishchi — loyiha nomi bilan. Nomsiz — root dagi ilova.

7.3 vazifalar-api ga monorepo kerakmi?

Kanon monorepo'ni sinamadi — loyiha uchun kerak emas. vazifalar-api da bitta ilova va bir nechta kirish nuqtasi bor: main.ts (HTTP), buyruq.ts (CLI). 29-qism integratsiyalari Nest'ga ko'chganda yana bittasi qo'shiladi — navbat ishchisi (Navbatlar: @nestjs/bullmq darsida). Hammasi bitta src/ va bitta package.json da, dist/ dan alohida fayl sifatida ishga tushadi. Monorepo qachon foydali:

Holat Yetarli yechim
bitta ilova + buyruqlar + ishchi bitta loyiha, bir nechta kirish fayli
bir nechta mustaqil deploy qilinadigan ilova, umumiy kod Nest CLI monorepo
Nest + frontend + boshqa paketlar birga npm workspaces + Turborepo/Nx

Oxirgi qator — IlmHamroh platformasining o'zi: Next.js va NestJS bitta repoda, npm workspaces va Turborepo bilan. Bu boshqa daraja — Nest CLI monorepo faqat Nest ilovalari uchun.

8. nest-commander — buyruqlar ko'p bo'lsa

buyruq.ts da ikkita buyruq bor va argumentlar process.argv bilan qo'lda o'qiladi. O'nta buyruq, bayroqlar (--format, --dan), yordam matni kerak bo'lsa — 24-qismdagi Commander kerak bo'ladi (Commander bilan buyruqlar tizimi). Nest uchun uning o'rami bor — nest-commander paketi: har buyruq — @Command({ name }) dekoratorli klass, CommandRunner dan meros oladi, bog'liqliklarni constructor orqali DI'dan oladi. Ishga tushirish — CommandFactory.run(AppModule).

Halol aytamiz: nest-commander'ni kanon ham, biz ham sinamadik. Ikki buyruq uchun qo'lda tahlil yetarli va bog'liqlik qo'shilmaydi. Buyruqlar ko'paysa — paketning rasmiy hujjatidan boshlang.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Buyruq ishini qildi, lekin tugamaydi ctx.close() yo'q — taymer, pool try/finally + await ctx.close()
Eksport fayli oxirida JSON qator log stdout'ga log darajasi yoki log — stderr'ga
Config xatosida jim exit 1 abortOnError sukut (true) abortOnError: false + catch
nest g app dan keyin src/ yo'q monorepo rejimi loyihani ko'chirdi avval commit; yangi yo'llarga moslang
npm run lint "No files found" lint skripti eski src/ ga qaraydi apps/ libs/ ga o'zgartiring

10. Mashqlar

Mashqlar kurs/mashqlar/30/52-standalone/ da.

1-mashq (oson): to'ldiring

  • HTTP'siz Nest konteksti: NestFactory.(AppModule).
  • Tayyor xizmatni olish: ctx.(VazifalarService).
  • Noto'g'ri buyruq — chiqish kodi [:64], noto'g'ri sozlama — [:78].
  • Ma'lumot qaysi oqimga yoziladi: [:stdout]; xabar va log — [:stderr].
Yechim
  • createApplicationContext — provider'lar va lifecycle bor, HTTP yo'q.
  • get — DI konteynerdagi nusxa.
  • 64 (EX_USAGE) va 78 (EX_CONFIG) — sysexits.h kelishuvi.
  • stdout — > fayl bilan faylga ketadi; stderr — ekranda qoladi.

2-mashq (o'rta): argumentlarni tahlil qilish

tahlil(argv) funksiyasini yozing. U { kod, buyruq, format, xabar } qaytarsin: teglar — kod: 0; eksport — format sukut "csv", faqat csv yoki json ruxsat; boshqa hamma holat — kod: 64 va foydalanish xabari. To'rtta holatni sinang. Ishora: massiv destructuring sukut qiymat bilan (Massiv destructuring).

Yechim
js
const YORDAM = "Foydalanish: buyruq teglar | eksport [csv|json]";

function tahlil(argv) {
  const [buyruq, format = "csv"] = argv;
  if (buyruq === "teglar") return { kod: 0, buyruq };
  if (buyruq === "eksport" && ["csv", "json"].includes(format)) {
    return { kod: 0, buyruq, format };
  }
  return { kod: 64, xabar: YORDAM };
}

for (const a of [["teglar"], ["eksport"], ["eksport", "xml"], []]) {
  console.log(JSON.stringify(a), "→", JSON.stringify(tahlil(a)));
}

Konsolda:

text
["teglar"] → {"kod":0,"buyruq":"teglar"}
["eksport"] → {"kod":0,"buyruq":"eksport","format":"csv"}
["eksport","xml"] → {"kod":64,"xabar":"Foydalanish: buyruq teglar | eksport [csv|json]"}
[] → {"kod":64,"xabar":"Foydalanish: buyruq teglar | eksport [csv|json]"}

Kanon fayli xml ni tekshirmaydi: eksport xml JSON yo'liga ham, CSV yo'liga ham to'liq tushmaydi — ma'lumot qatorlari JSON shaklida chiqadi, lekin [/] siz. Bu mashqdagi tekshiruv shu kichik bo'shliqni yopadi.

3-mashq (qiyin): ikki oqimli chiqish

Paketsiz model. buyruqYurgiz(oqimlar) — oqimlar da stdout va stderr massivlari bor. Funksiya CSV sarlavhasi va ikki qatorni stdout ga, "ombor ulanishlari yopildi" logini stderr ga yozsin. Keyin stdout ni "faylga" (bitta satrga \n bilan) yig'ing va qatorlar sonini hamda oxirgi qatorni chiqaring. Avval xato variantni (log ham stdout ga), keyin to'g'risini ko'rsating.

Yechim
js
function buyruqYurgiz(oqimlar, logOqimi) {
  oqimlar.stdout.push("id,matn");
  oqimlar.stdout.push("1,Non olish");
  oqimlar.stdout.push("2,Sut olish");
  const log = JSON.stringify({ msg: "ombor ulanishlari yopildi" });
  oqimlar[logOqimi].push(log);
}

for (const logOqimi of ["stdout", "stderr"]) {
  const oqimlar = { stdout: [], stderr: [] };
  buyruqYurgiz(oqimlar, logOqimi);
  const fayl = oqimlar.stdout.join("\n");
  const qatorlar = fayl.split("\n");
  console.log(`log → ${logOqimi}: faylda ${qatorlar.length} qator,`,
    `oxirgisi: ${qatorlar.at(-1)}`);
}

Konsolda:

text
log → stdout: faylda 4 qator, oxirgisi: {"msg":"ombor ulanishlari yopildi"}
log → stderr: faylda 3 qator, oxirgisi: 2,Sut olish

Haqiqiy loyihada buning bir yo'li — buyruq rejimida Pino'ni pino.destination(2) (2 — stderr fayl deskriptori) bilan yaratish. Logger'ni AppModule.forRoot({ log }) orqali berish mumkin — modul shu imkoniyatni testlar uchun allaqachon ochgan.

11. Loyiha qadami: npm run buyruq

Bu dars NestJS prodakshnda va NestJS versiyalarini yangilash bilan bitta qadam: vazifalar-api 2.0.0 versiyasiga tayyorlanadi. Commit — uchinchi darsda.

  1. Yangi fayl src/buyruq.ts — «src/buyruq.ts» bo'limidagi to'liq matn.
  2. package.json dagi scripts ga:
json
"buyruq": "node --env-file-if-exists=.env dist/buyruq.js"

Skript dist/ dan ishlaydi — avval npm run build. --env-file-if-exists — 24-qismdagi Node bayrog'i (--watch, --env-file va node --run): .env bo'lsa o'qiydi, bo'lmasa jim davom etadi. nest build src/ dagi hamma faylni yig'adi, buyruq.ts uchun alohida sozlama kerak emas.

Tekshiruv:

bash
npm run tip
npm run lint
npm run build
npm run buyruq -- teglar
npm run buyruq -- xyz; echo "exit $?"

Formatlangan buyruq.ts ni loyiha nusxasida tekshirdik: tsc --noEmit 0 xato, eslint 0, build o'tdi, «Natija» bo'limidagi chiqishlar shu build'dan. Hali commit qilmang.

12. Real ishda

  • Migratsiya va seed buyruqlari ko'p Nest loyihalarida standalone kontekst bilan yoziladi: npm run seed, npm run hisobot:oylik. Afzalligi — config va ombor bitta joyda.
  • Kubernetes CronJob / server cron'i buyruqni ishga tushiradi va chiqish kodiga qaraydi. Osilib qolgan jarayon — CronJob'ning keyingi ishga tushishini to'sadi.
  • Ishchi jarayon: navbat ishchisi ham standalone kontekst bo'lishi mumkin — HTTP'siz, faqat navbatni tinglaydi. Bu tanlovni Navbatlar: @nestjs/bullmq darsida o'lchov bilan ko'ramiz.
  • Intervyu: "Nest'da HTTP'siz skript qanday yoziladi?", "Nega skript tugamay qoladi?", "Nest monorepo va npm workspaces farqi?".

Xulosa

  • createApplicationContext — o'sha modullar, DI, config, lifecycle va taymerlar; HTTP, controller va middleware yo'q.
  • ctx.get(Xizmat) — tayyor xizmat; ish tugagach try/finally da await ctx.close() — aks holda @Interval va pool jarayonni ushlaydi.
  • CLI qoidalari: stdout — ma'lumot, stderr — log; chiqish kodlari 64 (EX_USAGE), 78 (EX_CONFIG).
  • Kanon buyrug'ida log stdout'ga tushadi — eksport fayli oxirida JSON qator; hozircha LOG_LEVEL=warn.
  • Nest CLI monorepo (nest g app/lib) loyihani apps/, libs/ ga ko'chiradi va rspack qo'yadi; vazifalar-api ga kerak emas.

Keyingi dars: NestJS prodakshnda — build (tsc va SWC), prod hajmi, Express va Nest o'lchovi, test qamrovi va check skriptiga build qo'shiladi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS standalone ilova: createApplicationContext, CLI buyruqlar va monorepo — IlmHamroh