IlmHamroh
JavaScript Full-stack/30-qism. NestJS4/65-dars29 daqiqa
Mundarija (41)

NestJS controller'lari: @Controller, @Get, @Param, @Body va @Res tuzog'i

Qisqacha: Controller — @Controller("yo'l") dekoratorli klass; uning metodlari @Get(), @Post(":id") kabi dekoratorlar bilan marshrutga aylanadi. So'rovdan kerakli qism parametr dekoratorlari bilan olinadi: @Param("id"), @Query(), @Body(), @Headers(), @Ip(). Metod qaytargan qiymatni Nest o'zi JSON qilib yuboradi. Status: POST — 201, qolgani — 200 (@HttpCode bilan o'zgartiriladi). @Res() qo'ysangiz, javobni yuborish sizning zimmangizga o'tadi — unutsangiz so'rov osilib qoladi; @Res({ passthrough: true }) esa sarlavha qo'yishga ruxsat beradi, javobni baribir Nest yuboradi.

Bu darsda

  • @Controller va metod dekoratorlari bilan marshrut yozasiz, Nest tuzgan marshrutlar jadvalini o'qiysiz.
  • Parametr dekoratorlari bilan so'rovning har qismini olasiz va ular doim satr ekanini bilasiz.
  • Status kodni (@HttpCode), sarlavhani (@Header) va yo'naltirishni (@Redirect) boshqarasiz.
  • @Res() tuzog'ini haqiqiy osilib qolgan so'rovda ko'rasiz va passthrough ni to'g'ri ishlatasiz.
  • vazifalar-api ning Express routerlarini Nest controller'lariga ko'chirasiz.

Oldin bilishingiz kerak: Modullar, req obyekti, res obyekti, Status kodlarni to'g'ri tanlash, express.Router va qatlamli loyiha tuzilmasi.

O'tgan darsda vazifalar-api da ikki bo'sh modul paydo bo'ldi — VazifalarModule va EksportModule. Endi ularning birinchi ichki qismi: HTTP so'rovni qabul qiladigan controller'lar.

1. Nega bu kerak?

25-qismdagi marshrut.ts da marshrut shunday yozilgan edi:

ts
r.route("/")
  .post(
    jsonKerak,
    tekshirib({ body: YangiVazifaSxemasi },
      async ({ body }, req, res) => {
        const v = await xizmat.yarat(body);
        vazifaSarlavhalari(res, v);
        res.status(201)
          .location(`${req.baseUrl}/${String(v.id)}`).json(v);
      }),
  )

Bu kodda uch xil ish aralashgan: yo'l va metod (r.route("/").post), kirishni tayyorlash (jsonKerak, tekshirib) va javobni yozish (res.status(201)…json(v)). Har marshrutda bu uchalasi qaytariladi. res.json() ni bir joyda unutsangiz — so'rov osilib qoladi.

Nest bu ishlarni ajratadi. Yo'l va metod — dekoratorda. So'rovning kerakli qismi — parametr dekoratorida. Javob — metodning oddiy return qiymati. Controller metodi oddiy funksiyaga o'xshab qoladi: kirish oladi, natija qaytaradi.

O'xshatish: restoran ofitsianti. Express'da ofitsiant buyurtmani o'zi yozadi, oshxonaga o'zi olib boradi, likopchani o'zi ko'taradi. Nest'da ofitsiant (controller) faqat "nima kerak" va "nima beraman" bilan shug'ullanadi; tarelka ko'tarish — restoran tizimining ishi.

2. Birinchi controller

2.1 Yo'l va metodlar

ts
@Controller('vazifalar')
class VazifalarController {
  @Get()
  royxat() { … }

  @Get(':id')
  ol(@Param('id') id: string) { … }

  @Post()
  yarat(@Body() body: unknown) { … }
}
  • @Controller('vazifalar') — hamma metodlar uchun umumiy boshlanish (prefiks). 25-qismdagi app.use("/vazifalar", router) ning o'rni.
  • @Get() — prefiksning o'zi: GET /vazifalar.
  • @Get(':id') — GET /vazifalar/7. :id — Express'dagi yo'l parametri, sintaksis bir xil (Express 5 route sintaksisi).
  • Boshqa metodlar: @Post, @Put, @Patch, @Delete, @Head, @Options va hammasini ushlaydigan @All.

Metod nomi (royxat, ol) Nest uchun ahamiyatsiz — marshrutni dekorator belgilaydi. Nomni o'qiydigan odam uchun tanlang.

2.2 Marshrutlar jadvali

Tajriba uchun ikki controller'li kichik ilova yozdik va uni ishga tushirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0; jarayon raqami va vaqt olib tashlandi). Ishga tushishda Nest har controller'ni va har marshrutni logga yozdi:

text
LOG [RoutesResolver] VazifalarController {/vazifalar}: +13ms
LOG [RouterExplorer] Mapped {/vazifalar, GET} route +5ms
LOG [RouterExplorer] Mapped {/vazifalar/:id, GET} route +2ms
LOG [RouterExplorer] Mapped {/vazifalar/toplam, GET} route +1ms
LOG [RouterExplorer] Mapped {/vazifalar, POST} route +1ms
LOG [RouterExplorer] Mapped {/vazifalar/tekshir, POST} route +10ms
LOG [RoutesResolver] TajribaController {/tajriba}: +0ms
LOG [RouterExplorer] Mapped {/tajriba/sarlavha, GET} route +1ms

Mapped {yo'l, METOD} — Nest dekoratorlardan tuzgan jadval. Tartibga qarang: aynan klassda yozilgan tartib. Bu keyinroq muhim bo'ladi — «Marshrut tartibi tuzog'i» bo'limida.

So'rov qanday yo'l bosadi:

sequenceDiagram
  participant M as Mijoz
  participant E as Express
  participant N as Nest router
  participant C as Controller metodi
  M->>E: GET /vazifalar/7?teg=uy
  E->>N: marshrut topildi
  N->>N: parametrlarni oladi (@Param, @Query)
  N->>C: ol("7")
  C-->>N: return { … }
  N->>E: res.status(200).json(…)
  E-->>M: 200 JSON

Diagrammadagi ikki joyga qarang. Parametrlarni metod emas, Nest chiqarib beradi. Javobni ham metod emas, Nest yuboradi: metod faqat qiymat qaytaradi.

3. So'rovning qismlarini olish

3.1 Parametr dekoratorlari

Dekorator Express'dagi Nima beradi
@Param('id') / @Param() req.params.id / req.params yo'l parametri
@Query('limit') / @Query() req.query.limit / req.query query satri
@Body('matn') / @Body() req.body.matn / req.body tana (JSON)
@Headers('x-til') req.get('x-til') sarlavha
@Ip() req.ip mijoz IP manzili
@Req() req butun Express so'rovi

Bular — legacy dekoratorlar darsidagi parametr dekoratorlari. Ular constructor emas, metod parametriga qo'yiladi. Nest har so'rovda ularni o'qib, metodni kerakli argumentlar bilan chaqiradi.

3.2 Tajriba: hammasi satr

Tajriba controller'i:

ts
@Get()
royxat(
  @Query() q: Record<string, string>,
  @Query('limit') limit?: string,
) {
  return { q, limit, turi: typeof limit };
}

@Get(':id')
ol(
  @Param('id') id: string,
  @Param() hammasi: Record<string, string>,
) {
  return { id, turi: typeof id, hammasi };
}

@Post()
yarat(@Body() body: unknown, @Body('matn') matn?: string) {
  return { body, matn };
}

fetch bilan yuborgan so'rovlarimiz va javoblar:

text
GET /vazifalar?limit=5&teg=uy → 200
  {"q":{"limit":"5","teg":"uy"},"limit":"5","turi":"string"}
GET /vazifalar/7 → 200
  {"id":"7","turi":"string","hammasi":{"id":"7"}}
POST /vazifalar → 201
  {"body":{"matn":"Non olish"},"matn":"Non olish"}

Asosiy saboq: limit — "5", id — "7". Satr. Metod imzosida id: number deb yozsangiz ham, keladigan qiymat baribir satr bo'ladi — TypeScript tipi ish vaqtida hech narsani o'zgartirmaydi. Bu Express'da ham shunday edi (req obyekti). Satrni songa aylantirish va tekshirish — pipe'larning ishi (Pipe'lar).

@Body() bilan kelgan narsa — mijoz yuborgan har qanday JSON. Uni unknown deb olish to'g'ri (Express + TypeScript darsidagi qoida): tekshirilmaguncha unga ishonmaymiz.

Diqqat: @Body() body: YangiVazifa deb tur yozish — tekshiruv emas. Hujumchi {"matn": 5, "admin": true} yuborsa, u shu "tur" bilan metodga kiradi. Tekshiruv kodi bo'lmasa — tur yolg'on gapiradi.

3.3 @Headers() va @Ip()

ts
@Get('sarlavha')
@Header('Cache-Control', 'no-store')
sarlavha(@Headers('x-til') til?: string, @Ip() ip?: string) {
  return { til, ip };
}

x-til: uz sarlavhasi bilan so'rov:

text
GET /tajriba/sarlavha → 200 cache-control: no-store
  {"til":"uz","ip":"127.0.0.1"}

@Headers('x-til') — sarlavha nomi kichik harf bilan (Node sarlavhalarni kichik harfga o'tkazadi). @Ip() — Express'ning req.ip i. Server proxy orqasida bo'lsa, u trust proxy sozlamasiga bog'liq (Reverse proxy orqasida Node) — aks holda proxy'ning IP'sini ko'rasiz.

3.4 @Req() — butun so'rov

text
GET /tajriba/req?a=1 → 200
  {"metod":"GET","yol":"/tajriba/req","url":"/tajriba/req?a=1"}

@Req() Express'ning req obyektini to'liq beradi. Qulay, lekin controller Express'ga bog'lanadi: Fastify adapterida req boshqacha. Nest hujjati ham imkon bo'lsa aniq dekoratorlarni (@Param, @Body) ishlatishni tavsiya qiladi. vazifalar-api da hozircha @Req() ishlatamiz — sababini «Loyiha qadami» da ko'ramiz.

Tekshirib ko'ring: @Get(':id') ol(@Param('id') id: number) metodida id === 7 tekshiruvi GET /vazifalar/7 da nima beradi?

Javob

false. id aslida "7" (satr), "7" === 7 — false. TypeScript number deb ishonadi, lekin ish vaqtida turni hech kim o'zgartirmagan. Tajribada turi: "string" chiqdi. Songa aylantirish uchun pipe kerak (ParseIntPipe) yoki qo'lda Number(id) va tekshiruv.

4. Javob: status, sarlavha, yo'naltirish

4.1 Qaytarilgan qiymat va sukut status

Metod nima qaytarsa, Nest shuni yuboradi:

  • obyekt yoki massiv → JSON (Content-Type: application/json);
  • satr → matn (text/html, 1-darsdagi Hello World!);
  • Promise → kutadi, keyin natijani yuboradi (async metodlar);
  • undefined → bo'sh tana.

Tajribadan ikki misol:

text
GET /tajriba/async → 200
  ["osh","manti"]
GET /tajriba/bosh → 200 content-length: 0
  (bo'sh)

Sukut status: POST — 201, qolgan hammasi — 200. Tajribadagi POST /vazifalar — 201, garchi biz status yozmagan bo'lsak ham.

4.2 @HttpCode

POST har doim ham "yaratildi" emas. Masalan, "tekshirib ber" degan so'rov hech narsa yaratmaydi — unga 200 to'g'ri (Status kodlarni to'g'ri tanlash):

ts
@Post('tekshir')
@HttpCode(200)
tekshir(@Body() body: unknown) {
  return { ok: true, body };
}
text
POST /vazifalar/tekshir → 200
  {"ok":true,"body":{"matn":"x"}}

vazifalar-api da ikki joyda kerak: DELETE — @HttpCode(204) (tana yo'q), eksportni boshlash — @HttpCode(202) (qabul qilindi, keyin bajariladi — Bulk va uzoq davom etuvchi operatsiyalar).

4.3 @Header

@Header('Cache-Control', 'no-store') — har javobga sarlavha qo'shadi (yuqoridagi tajribada cache-control: no-store chiqdi). Qiymat o'zgarmas bo'lsa qulay. Qiymat so'rovga bog'liq bo'lsa (Location: /v1/vazifalar/49, ETag: "3") — @Header yetmaydi, javob obyekti kerak (keyingi bo'lim).

Kanon bitta farqni o'lchagan: @Header sarlavhani handler'dan oldin qo'yadi. Shuning uchun handler xato tashlasa ham (masalan 404), sarlavha javobda qoladi. Express'da res.set() handler ichida bo'lgani uchun faqat muvaffaqiyatli javobda edi. Kesh sarlavhalari uchun bu zararsiz, lekin bilib qo'ying.

4.4 @Redirect

ts
@Get('eski')
@Redirect('https://example.com/yangi', 301)
eski() {}

@Get('dinamik')
@Redirect('https://example.com', 302)
dinamik(@Query('v') v?: string) {
  if (v === '2') return { url: 'https://example.com/v2' };
}
text
GET /tajriba/eski → 301 location: https://example.com/yangi
  Moved Permanently. Redirecting to https://example.com/yangi
GET /tajriba/dinamik?v=2 → 302 location: https://example.com/v2
  Found. Redirecting to https://example.com/v2

Ikkinchi holatda metod { url } qaytardi va u dekoratordagi manzilni almashtirdi. { url, statusCode } qaytarsangiz, status ham almashadi. Hech narsa qaytarmasa — dekoratordagi qiymat qoladi. Tanadagi Moved Permanently. Redirecting to … — Express'ning res.redirect() matni: ichida haqiqatan Express ishlayapti.

Diqqat: Foydalanuvchidan kelgan manzilga (?qayt=https://...) ko'r-ko'rona yo'naltirmang — bu open redirect zaifligi: hujumchi sizning domeningiz nomidan fishing sahifasiga havola tarqatadi. Faqat ruxsat ro'yxatidagi manzillarga yo'naltiring.

5. @Res() tuzog'i

5.1 Javobni o'zingiz yuborish

Ba'zan Express javob obyekti kerak: dinamik sarlavha, fayl, oqim. @Res() uni beradi:

ts
@Get('res')
res(@Res() res: Response) {
  res.status(200).json({ qolda: true });
}

@Get('res-unutildi')
resUnutildi(@Res() _res: Response) {
  return { kelmaydi: true };
}

Ikkinchi metod qiymat qaytardi, lekin res ga hech narsa yozmadi. Mijozga 500 ms vaqt berdik (AbortSignal.timeout(500)):

text
GET /tajriba/res → 200
  {"qolda":true}
GET /tajriba/res-unutildi → TimeoutError

Javob kelmadi. Sabab: @Res() qo'yilgan metodda Nest "javobni dasturchi o'zi yuboradi" deb hisoblaydi va qaytarilgan qiymatni e'tiborsiz qoldiradi. Hech kim res.json() chaqirmadi — so'rov ochiq qoldi. Xato ham, log ham yo'q. Haqiqiy serverda bu ulanish timeout'gacha (25-qismdagi requestTimeout) band turadi (Server timeout'lari).

5.2 passthrough: true

Yechim — @Res({ passthrough: true }): javob obyekti qo'lingizda, lekin tanani baribir Nest yuboradi:

ts
@Get('passthrough')
passthrough(@Res({ passthrough: true }) res: Response) {
  res.set('X-Versiya', '7');
  return { nest: 'yubordi' };
}
text
GET /tajriba/passthrough → 200 x-versiya: 7
  {"nest":"yubordi"}

Sarlavha qo'yildi va tana keldi. Qoida:

Yozuv Javobni kim yuboradi return
@Res() siz (res.json, res.send) e'tiborsiz
@Res({ passthrough: true }) Nest javob tanasi
hech qaysi Nest javob tanasi

passthrough da res.status(207) ham ishlaydi — Nest siz qo'ygan statusni saqlaydi. Kanon buni /toplam uchun o'lchab tasdiqlagan (toplam.test).

Tekshirib ko'ring: Hamkasbingiz CSV yuborish uchun @Res() res oldi, res.setHeader(...) qildi va return csvMatn yozdi. Brauzer "yuklanmoqda…" holatida qotib qoldi. Nima bo'ldi va qanday tuzatiladi?

Javob

@Res() (passthrough'siz) bilan return e'tiborsiz — tana hech qachon yuborilmadi. Ikki yechim: res.send(csvMatn) ni o'zi chaqirsin yoki @Res({ passthrough: true }) qilib return csvMatn qoldirsin. Ikkinchisi yaxshiroq: interceptor va filtrlar (keyingi darslar) Nest yuborgan javob bilan ishlaydi.

6. Marshrut tartibi tuzog'i

Tajriba controller'ida @Get(':id') @Get('toplam') dan oldin yozilgan edi. So'rov:

text
GET /vazifalar/toplam → 200
  {"id":"toplam","turi":"string","hammasi":{"id":"toplam"}}

toplam marshrutiga yetib bormadi — uni :id ushlab oldi va id = "toplam" deb ishladi. Express marshrutlarni ro'yxatga olingan tartibda tekshiradi va birinchi moslikda to'xtaydi. Nest esa ularni klassdagi tartibda ro'yxatga oladi (jadvalda ham /vazifalar/:id /vazifalar/toplam dan oldin edi).

Buni oddiy JS modelida ko'raylik — blok brauzerda ishlaydi:

js
function marshrutlar(royxat) {
  const naqsh = (yol) => new RegExp(
    "^" + yol.replace(/:(\w+)/g, "(?<$1>[^/]+)") + "$",
  );
  return function top(metod, url) {
    for (const m of royxat) {
      if (m.metod !== metod) continue;
      const mos = naqsh(m.yol).exec(url);
      if (mos) return `${m.nomi} ${JSON.stringify(mos.groups ?? {})}`;
    }
    return "404";
  };
}

const notogri = marshrutlar([
  { metod: "GET", yol: "/vazifalar/:id", nomi: "ol" },
  { metod: "GET", yol: "/vazifalar/toplam", nomi: "toplam" },
]);
const togri = marshrutlar([
  { metod: "GET", yol: "/vazifalar/toplam", nomi: "toplam" },
  { metod: "GET", yol: "/vazifalar/:id", nomi: "ol" },
]);

console.log(notogri("GET", "/vazifalar/toplam"));
console.log(togri("GET", "/vazifalar/toplam"));
console.log(togri("GET", "/vazifalar/7"));
console.log(togri("DELETE", "/vazifalar/7"));

Konsolda:

text
ol {"id":"toplam"}
toplam {}
ol {"id":"7"}
404

Birinchi qator — tajribadagi xato aynan: :id naqshi toplam so'zini ham "id" deb oldi. Ikkinchi qatorda aniq yo'l oldinda — to'g'ri ishladi, uchinchisida esa 7 hali ham ol ga bordi. Qoida: aniq yo'llar (toplam) — parametrli yo'llardan (:id) oldin. Kanon controller'ida shuning uchun izoh bor: "/:id dan OLDIN — aks holda toplam id deb olinadi".

To'rtinchi qator — DELETE uchun marshrut yo'q, 404. Haqiqiy Nest ham shunday qildi:

text
DELETE /vazifalar/7 → 404
  {"message":"Cannot DELETE /vazifalar/7","error":"Not Found","statusCode":404}

Yo'l bor, faqat metod yo'q — HTTP bo'yicha bu 405 Method Not Allowed va Allow sarlavhasi bo'lishi kerak edi (HTTP metodlari semantikasi). Express ham, Nest ham buni o'zi qilmaydi. 25-qismda .all(metodYoq(...)) bilan qilgandik; Nest'da uning o'rni — @All() (pastda).

7. vazifalar-api ning controller'lari

7.1 Express routeri → Nest controller

25–27 (Express) Nest controller
vazifalarRouteri(xizmat, { tz }) constructor(xizmat, sozlamalar)
r.route("/").get(...) @Get() royxat(...)
r.route("/:id").delete(...) @Delete(":id") @HttpCode(204)
.all(metodYoq("GET, HEAD")) @All() metodYoq(...)
res.status(201).json(v) return v (POST — 201)
jsonKerak (marshrut middleware'i) jsonTanaKerak(req) handler boshida
req.baseUrl asosYol(req, "vazifalar")

Oxirgi ikki qatorni tushuntiramiz.

jsonTanaKerak. Express'da jsonKerak — marshrut darajasidagi middleware edi: .post(jsonKerak, …). Nest'da marshrut darajasidagi middleware yo'q (uning o'rnini keyinroq guard bosadi — Guard'lar). Hozircha bir xil tekshiruvni handler boshida oddiy funksiya qilib chaqiramiz. Tartib Express'dagidek qoladi: 415 → params → query → body.

asosYol. Express'da req.baseUrl router ulangan joyni berardi: /v1/vazifalar. Nest marshrutlarni routerga emas, ilovaning o'ziga ulaydi — req.baseUrl bo'sh. Kanon to'plam yo'lini joriy so'rov yo'lidan chiqaradigan kichik funksiya yozdi. U toza funksiya — brauzerda sinab ko'ramiz:

js
function asosYol(req, toplam) {
  const yol = req.path;
  const i = yol.indexOf(`/${toplam}`);
  return i === -1
    ? `/${toplam}`
    : yol.slice(0, i + toplam.length + 1);
}

console.log(asosYol({ path: "/v1/vazifalar/5" }, "vazifalar"));
console.log(asosYol({ path: "/vazifalar/5" }, "vazifalar"));
console.log(asosYol({ path: "/v1/vazifalar" }, "vazifalar"));
console.log(asosYol({ path: "/salom" }, "vazifalar"));

Konsolda:

text
/v1/vazifalar
/vazifalar
/v1/vazifalar
/vazifalar

Versiyali (/v1/…) va eskirgan versiyasiz (/vazifalar/…) yo'l — ikkalasi to'g'ri. Bitta controller ikkala yo'lda qanday ishlashi — enableVersioning sozlamasi, Bootstrap: main.ts darsida.

7.2 Token orqali bog'liqlik berish — birinchi uchrashuv

VazifalarController constructor'ida ikki bog'liqlik bor:

ts
constructor(
  private readonly xizmat: VazifalarService,
  @Inject(ILOVA_SOZLAMALARI)
  private readonly sozlamalar: IlovaSozlamalari,
) {}

Birinchisi — klass, Nest uni turidan taniydi (1-darsdagi design:paramtypes). Ikkinchisi — IlovaSozlamalari — oddiy TypeScript turi ({ tz: string }). Ish vaqtida tur yo'q, Nest uni taniy olmaydi. Shuning uchun token beriladi: @Inject(ILOVA_SOZLAMALARI) — "menga shu nomli narsani bering". Token — Symbol, tokenlar.ts faylida. Token ortida qaysi qiymat turishini kim aytadi — Provider'lar va Custom provider'lar darslarida.

7.3 @Req() va @Res({ passthrough: true }) nega

Kanon controller'larida deyarli har metod @Req() va @Res({ passthrough: true }) oladi. Bu ataylab qilingan vaqtinchalik shakl: Express kodi ko'chirilganda mantiq o'zgarmasin. Eski yordamchilar (vazifaSarlavhalari(res, v), keshYoq(res), ifMatch(req)) Express obyektlarini kutadi. Kirish tekshiruvi ham eski Zod tekshir() bilan — handler ichida.

Keyingi darslarda bu shakl tozalanadi: kirish — pipe va DTO'ga (Zod va Standard Schema NestJS'da), sarlavhalar — interceptor va @Header ga. Hozirgi maqsad — HTTP xulqi aynan qolsin: 25–27 dagi testlar o'zgarmasdan o'tsin.

7.4 405 — @All()

ts
@All(":id")
birMetodYoq(@Req() req: Request): never {
  throw new MetodYoqXatosi(
    req.method, "GET, HEAD, PUT, PATCH, DELETE",
  );
}

@All(":id") — shu yo'ldagi qolgan hamma metodlar. U aniq metodlardan (@Get(":id"), @Delete(":id")) keyin yozilishi shart. Aks holda u ularni ham ushlab oladi — «Marshrut tartibi tuzog'i» bo'limidagi qoidaning o'zi. MetodYoqXatosi — 25-qismdagi xato klassi (Xato turlari); uni Problem Details javobiga aylantirish — filtrning ishi (Exception filter'lar).

Kanon bitta Nest kamchiligini topgan: @All() OpenAPI hujjatida haqiqiy metodlar ustidan yozib yuboradi. Uni Swagger darsida ko'ramiz.

8. Ko'chish natijasi: jonli so'rovlar

VazifalarService va bootstrap keyingi ikki darsda qo'shiladi. Natijani oldindan ko'raylik. Ko'chish tugagan holatni kanon nusxasida build qilib, xotira rejimida yurgizdik (Node 24.21.0, NestJS 12.2.0, SAQLASH=xotira). helmet sarlavhalari, Date, ETag, X-Request-Id qatorlarini olib tashladik, manzildagi http://127.0.0.1:<port> ni va uzun tanalarni … bilan qisqartirdik:

text
$ curl -X POST -H 'Content-Type: application/json' -d '{"matn":"Non olish #bozor"}' /v1/vazifalar
HTTP/1.1 201 Created
Location: /v1/vazifalar/1
{"id":1,"matn":"Non olish #bozor","bajarildi":false,"teglar":["bozor"],…,"versiya":1}

$ curl -X POST … -d '{"vazifalar":[{"matn":"Choy damlash"},{"matn":""}]}' /v1/vazifalar/toplam
HTTP/1.1 207 Multi-Status
{"natijalar":[{"indeks":0,"holat":201,"vazifa":{"id":2,…}},{"indeks":1,"holat":422,"xato":{"joy":"body.vazifalar[1]","xabar":"matn 1–100 belgi bo'lishi kerak"}}]}

$ curl -X DELETE /v1/vazifalar
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, POST, PUT
{"type":"about:blank","title":"Method Not Allowed","status":405,"detail":"DELETE bu manzilda yo'q","instance":"/v1/vazifalar",…}

$ curl /v1/vazifalar/abc
HTTP/1.1 400 Bad Request
{"type":"https://vazifalar-api.example/xatolar/parametr","title":"Parametr noto'g'ri","status":400,"detail":"id — musbat butun son",…}

$ curl -X POST -H 'Content-Type: text/plain' -d 'salom' /v1/vazifalar
HTTP/1.1 415 Unsupported Media Type
{"type":"about:blank","title":"Unsupported Media Type","status":415,"detail":"Content-Type: application/json bo'lishi kerak",…}

$ curl /vazifalar/1
HTTP/1.1 200 OK
Deprecation: @1792800000
Sunset: Sun, 24 Jan 2027 00:00:00 GMT
Link: </v1/vazifalar/1>; rel="successor-version"

Har javobni Express'dagi bilan solishtiring:

  • 201 + Location — return v va res.location(...); status yozilmagan, POST sukuti.
  • 207 — toplam :id dan oldin turgani uchun yetib bordi; res.status(207) passthrough bilan saqlandi.
  • 405 + Allow — @All(); Nest'ning o'z 404 i chiqmadi.
  • 400 — abc satr keldi va Zod uni rad etdi (@Param doim satr).
  • 415 — jsonTanaKerak(req) handler boshida.
  • Oxirgisi — versiyasiz eski yo'l ham shu controller'ga keldi va eskirgan sarlavhalarini oldi (API versiyalash).

Kanon xuddi shu ko'chishni 9 ta so'rov bilan uch saqlash rejimida tekshirgan: Express versiyasidan farq faqat port va vaqt qatorlarida (status, sarlavhalar, Problem Details tanasi aynan). 25–27 dagi testlar ham o'zgarmasdan o'tgan.

9. Hujumchi nigohi

Controller — tashqi dunyo bilan birinchi uchrashuv joyi. Hujumchi nima qiladi:

  1. Turga ishonishga undaydi. @Body() body: YangiVazifa — tekshiruvsiz bo'lsa, {"matn": {"$ne": 1}} yoki ortiqcha "egasi_id" maydoni metodga kiradi. Himoya — har kirish sxema bilan tekshiriladi (kanon: Zod tekshir(); keyin pipe).
  2. Parametr bilan o'ynaydi. /vazifalar/-1, /vazifalar/1e9, /vazifalar/toplam. Himoya — IdSxemasi (musbat butun son) va to'g'ri marshrut tartibi.
  3. Yo'naltirishdan foydalanadi. @Redirect + foydalanuvchi manzili — open redirect. Himoya — ruxsat ro'yxati.
  4. Javobni osiltiradi. @Res() unutilgan marshrut — har so'rov ulanishni timeout'gacha band qiladi. Ko'p so'rov — server ulanishlari tugaydi. Himoya — passthrough yoki return.
  5. IP'ni soxtalashtiradi. X-Forwarded-For: 1.2.3.4 — trust proxy noto'g'ri bo'lsa @Ip() yolg'on qiymat beradi va tezlik cheklovi aldanadi. Himoya — trust proxy ni aniq proxy soniga sozlash (Reverse proxy).

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
So'rov javobsiz osilib qoladi @Res() + return, res.json() yo'q @Res({ passthrough: true }) yoki res.send
/vazifalar/toplam id deb olinadi :id aniq yo'ldan oldin aniq yo'llarni tepaga
id === 7 har doim false @Param satr beradi Number() + tekshiruv yoki pipe
Cannot DELETE /x (404) metod yo'q @All() bilan 405 + Allow
Marshrut ishlamaydi, Mapped logda yo'q controller modulga yozilmagan modulning controllers iga qo'shing
POST 200 o'rniga 201 qaytaradi POST sukuti 201 @HttpCode(200)

11. Mashqlar

Mashqlar kurs/mashqlar/30/04-controllerlar/ da.

1-mashq (oson): dekoratorni toping

  • GET /vazifalar?teg=uy dan teg ni olish — @("teg")
  • PATCH /vazifalar/5 dan 5 ni olish — @("id")
  • POST'ning sukut statusini 200 ga o'zgartirish — @(200)
  • Javob obyektini olib, tanani Nest'ga qoldirish — @Res({ : true })
  • GET /vazifalar/7 da @Param("id") ning JavaScript turi —
Yechim
  • Query — @Query("teg"), hammasi kerak bo'lsa @Query().
  • Param — yo'l parametri; marshrut @Patch(":id").
  • HttpCode — @HttpCode(200).
  • passthrough — @Res({ passthrough: true }).
  • string — "7"; songa pipe yoki Number() aylantiradi.

2-mashq (o'rta): marshrutlarni avtomatik tartiblash

Darsdagi marshrutlar modeliga tartib xatosidan himoya qo'shing: ro'yxatni tekshirishdan oldin shunday saralang — parametrsiz yo'llar parametrlilardan oldin, qolgani o'z tartibida. Shunda :id qayerda yozilganidan qat'i nazar toplam to'g'ri ishlaydi. Ishora: toSorted (saralash) va "yo'lda : bormi" — includes(":").

Yechim
js
function marshrutlar(royxat) {
  const naqsh = (yol) => new RegExp(
    "^" + yol.replace(/:(\w+)/g, "(?<$1>[^/]+)") + "$",
  );
  const parametrli = (m) => (m.yol.includes(":") ? 1 : 0);
  const tartibli = royxat.toSorted(
    (a, b) => parametrli(a) - parametrli(b),
  );
  return function top(metod, url) {
    for (const m of tartibli) {
      if (m.metod !== metod) continue;
      const mos = naqsh(m.yol).exec(url);
      if (mos) return `${m.nomi} ${JSON.stringify(mos.groups ?? {})}`;
    }
    return "404";
  };
}

const top = marshrutlar([
  { metod: "GET", yol: "/vazifalar/:id", nomi: "ol" },
  { metod: "GET", yol: "/vazifalar/toplam", nomi: "toplam" },
  { metod: "GET", yol: "/vazifalar", nomi: "royxat" },
]);
console.log(top("GET", "/vazifalar/toplam"));
console.log(top("GET", "/vazifalar/7"));
console.log(top("GET", "/vazifalar"));

Konsolda:

text
toplam {}
ol {"id":"7"}
royxat {}

toSorted barqaror: teng elementlar (ikkalasi parametrsiz) o'z tartibini saqlaydi. Express va Nest bunday saralashni qilmaydi — ular siz yozgan tartibga ishonadi. Ba'zi routerlar aniq yo'lni har doim ustun qo'yadi: masalan, Fastify ichidagi find-my-way statik yo'lni parametrlidan oldin tekshiradi (uning hujjati bo'yicha). Nest'da esa tartib — sizning mas'uliyatingiz.

3-mashq (qiyin): Allow sarlavhasini hisoblash

Kanon controller'ida Allow qiymatlari qo'lda yozilgan: "GET, HEAD, PUT, PATCH, DELETE". Bunday ro'yxat marshrut qo'shilganda eskirib qoladi. ruxsatlar(royxat, url) funksiyasini yozing: marshrutlar ro'yxatidan shu URL'ga mos keladigan hamma metodlarni yig'ib, Allow qatorini qaytarsin. GET bo'lsa, HEAD ham qo'shilsin (Express GET marshrutiga HEAD'ni avtomatik javob beradi). Tartib: GET, HEAD, POST, PUT, PATCH, DELETE. Hech biri mos kelmasa — null (bu 404). Ishora: Set (Set), filter.

Yechim
js
const TARTIB = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
const naqsh = (yol) => new RegExp(
  "^" + yol.replace(/:(\w+)/g, "[^/]+") + "$",
);

function ruxsatlar(royxat, url) {
  const metodlar = new Set(
    royxat.filter((m) => naqsh(m.yol).test(url)).map((m) => m.metod),
  );
  if (metodlar.size === 0) return null;
  if (metodlar.has("GET")) metodlar.add("HEAD");
  return TARTIB.filter((m) => metodlar.has(m)).join(", ");
}

const royxat = [
  { metod: "GET", yol: "/vazifalar" },
  { metod: "POST", yol: "/vazifalar" },
  { metod: "PUT", yol: "/vazifalar" },
  { metod: "POST", yol: "/vazifalar/toplam" },
  { metod: "GET", yol: "/vazifalar/:id" },
  { metod: "PUT", yol: "/vazifalar/:id" },
  { metod: "PATCH", yol: "/vazifalar/:id" },
  { metod: "DELETE", yol: "/vazifalar/:id" },
];
console.log(ruxsatlar(royxat, "/vazifalar"));
console.log(ruxsatlar(royxat, "/vazifalar/5"));
console.log(ruxsatlar(royxat, "/teglar"));

Konsolda:

text
GET, HEAD, POST, PUT
GET, HEAD, PUT, PATCH, DELETE
null

Birinchi ikki qator kanon controller'idagi qo'lda yozilgan qiymatlar bilan aynan bir xil — ular to'g'ri ekan. E'tibor bering: /vazifalar/toplam uchun bu funksiya GET, HEAD, POST, PUT, PATCH, DELETE beradi — chunki :id naqshi toplam ga ham mos keladi. Shuning uchun haqiqiy loyihada marshrut jadvalini avtomatik tekshirish testda qilinadi, javob paytida emas. Kanon buni keyinroq DiscoveryService bilan qiladi (Reflector va DiscoveryService).

12. Loyiha qadami: controller'lar

12.1 Qaysi fayllar

Bu dars — butun v1 API'ni ko'chirishning ikkinchi qismi. Yangi fayllar:

Fayl Nima
src/tokenlar.ts DI tokenlari (Symbol)
src/umumiy/yol.ts asosYol
src/vazifalar/vazifalar.controller.ts /vazifalar marshrutlari
src/vazifalar/teglar.controller.ts /teglar
src/eksport/eksport.controller.ts /eksportlar

O'zgaradigan: umumiy/json-kerak.ts (yangi funksiya), ikki modul fayli. Kanon fayllarida qatorlar uzun edi — telefon uchun bo'ldik (izohlar qisqartirildi, kod ma'nosi aynan). Bo'lingan nusxani biz kanon nusxasida tsc --noEmit, ESLint va Vitest bilan tekshirdik — o'zgarishsiz o'tdi.

12.2 tokenlar.ts va yol.ts

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 IDEMPOTENTLIK_OMBORI = Symbol("IDEMPOTENTLIK_OMBORI");
export const LOG = Symbol("LOG");
export const XABARCHI = Symbol("XABARCHI");
export const ILOVA_SOZLAMALARI = Symbol("ILOVA_SOZLAMALARI");

/** Controller'larga kerakli kichik sozlamalar (to'liq Config —
 * keyinroq ConfigModule bilan) */
export type IlovaSozlamalari = { tz: string };
ts
// yol.ts — resurs to'plamining yo'li (Location, Link uchun).
// Express'da req.baseUrl ("/v1/vazifalar") router ulangan joyni
// berardi; Nest marshrutlarni ilovaning o'ziga ro'yxatdan o'tkazadi —
// baseUrl bo'sh. Yo'lni joriy so'rovdan olamiz: versiyali
// (/v1/vazifalar) va eskirgan (/vazifalar) yo'llar ikkalasi to'g'ri
import type { Request } from "express";

/** "/v1/vazifalar/5/x" va "vazifalar" → "/v1/vazifalar" */
export function asosYol(req: Request, toplam: string): string {
  const yol = req.path;
  const i = yol.indexOf(`/${toplam}`);
  return i === -1
    ? `/${toplam}`
    : yol.slice(0, i + toplam.length + 1);
}

12.3 json-kerak.ts

Eski jsonKerak middleware'i qoladi — Express API hali u bilan ishlaydi. Faylga yangi funksiya qo'shiladi (import qatori ham Request turini oladi):

ts
import type { Request, RequestHandler } from "express";

/** Nest controller'lari uchun: handler boshida chaqiriladi */
export function jsonTanaKerak(req: Request): void {
  const tur = req.is(JSON_TURLARI);
  if (tur === false || tur === null) {
    throw new IlovaXatosi(
      415, "Content-Type: application/json bo'lishi kerak",
    );
  }
}

Express routeri o'chirilganda (Bootstrap: main.ts darsi) jsonKerak ham olib tashlanadi va faylda faqat jsonTanaKerak qoladi — kanon yakuniy fayli shunday.

12.4 Controller'lar

src/vazifalar/vazifalar.controller.ts:

ts
// vazifalar.controller.ts — /v1/vazifalar: HTTP ↔ xizmat (25-qism
// marshrut.ts dan). Hozircha kirish eski Zod `tekshir()` bilan
// handler ichida tekshiriladi (tartib Express'dagidek: 415 →
// params → query → body); keyinroq DTO va pipe'larga o'tadi.
// @Res({ passthrough: true }) — sarlavha qo'yish uchun Express
// javobi, lekin javob tanasini Nest o'zi yuboradi (return qiymati)
import {
  All, Controller, Delete, Get, HttpCode, Inject, Patch, Post, Put,
  Req, Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { MetodYoqXatosi } from "../umumiy/xatolar.ts";
import type { MaydonXatosi } from "../umumiy/xatolar.ts";
import { jsonTanaKerak } from "../umumiy/json-kerak.ts";
import { kursorYoz } from "../umumiy/kursor.ts";
import {
  ifMatch, keshYoq, vazifaSarlavhalari,
} from "../umumiy/shartli.ts";
import { tekshir } from "../umumiy/tekshir.ts";
import { asosYol } from "../umumiy/yol.ts";
import {
  BulkSxemasi, IdSxemasi, OzgarishSxemasi, RoyxatQuerySxemasi,
  ToliqVazifaSxemasi, ToplamSxemasi, YangiVazifaSxemasi,
} from "./sxema.ts";
import type { Vazifa } from "./vazifa.ts";
import { proyeksiya, royxatSorovi } from "./sorov.ts";
import { VazifalarService } from "./vazifalar.service.ts";
import { ILOVA_SOZLAMALARI } from "../tokenlar.ts";
import type { IlovaSozlamalari } from "../tokenlar.ts";

/** Keyingi sahifa havolasi: query nusxasi, offset/cursor yangi */
function keyingiHavola(
  req: Request,
  ozgartir: Record<string, string>,
): string {
  const p = new URLSearchParams(req.originalUrl.split("?")[1] ?? "");
  p.delete("offset");
  p.delete("cursor");
  for (const [k, v] of Object.entries(ozgartir)) p.set(k, v);
  return `${asosYol(req, "vazifalar")}?${p.toString()}`;
}

type Natija = {
  indeks: number;
  holat: number;
  vazifa?: Vazifa;
  xato?: MaydonXatosi;
};

// Versiya — ilova.ts dagi enableVersioning (URI): /v1/vazifalar
// va eskirgan /vazifalar
@Controller("vazifalar")
export class VazifalarController {
  constructor(
    private readonly xizmat: VazifalarService,
    @Inject(ILOVA_SOZLAMALARI)
    private readonly sozlamalar: IlovaSozlamalari,
  ) {}

  @Get()
  async royxat(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<object> {
    const query = tekshir(RoyxatQuerySxemasi, req.query, "query");
    const { sorov, maydonlar, imzo } =
      royxatSorovi(query, this.sozlamalar.tz);
    const natija = await this.xizmat.royxat(sorov);
    const sahifa: Record<string, unknown> = { limit: sorov.limit };
    let keyingi: string | undefined;
    if (sorov.sahifa.tur === "offset") {
      sahifa.offset = sorov.sahifa.offset;
      if (natija.borKeyingi) {
        const offset = String(sorov.sahifa.offset + sorov.limit);
        keyingi = keyingiHavola(req, { offset });
      }
    } else {
      const kursor =
        natija.keyingi && kursorYoz(natija.keyingi, imzo);
      sahifa.keyingi = kursor ?? null;
      if (kursor !== undefined) {
        keyingi = keyingiHavola(req, { cursor: kursor });
      }
    }
    if (natija.jami !== undefined) sahifa.jami = natija.jami;
    // Link sarlavhasi (RFC 8288) — mijoz URL'ni o'zi yig'masin
    if (keyingi !== undefined) res.links({ next: keyingi });
    // ro'yxat ETag'i — Express'niki (tana xeshi, W/"…"): Nest javobni
    // res.json() bilan yuboradi → If-None-Match mos kelsa 304
    keshYoq(res);
    return {
      versiya: natija.versiya,
      vazifalar: natija.vazifalar.map(
        (v) => proyeksiya(v, maydonlar),
      ),
      sahifa,
    };
  }

  @Post()
  async yarat(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<Vazifa> {
    jsonTanaKerak(req);
    const body = tekshir(YangiVazifaSxemasi, req.body, "body");
    const v = await this.xizmat.yarat(body);
    vazifaSarlavhalari(res, v);
    res.location(`${asosYol(req, "vazifalar")}/${String(v.id)}`);
    return v; // POST — sukut 201
  }

  /** PUT to'plamga — butun ro'yxatni almashtirish; atomik */
  @Put()
  async almashtirHammasi(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<object> {
    jsonTanaKerak(req);
    const body = tekshir(ToplamSxemasi, req.body, "body");
    const { vazifalar, versiya } =
      await this.xizmat.almashtirHammasi(body.vazifalar);
    keshYoq(res);
    return { versiya, vazifalar };
  }

  // Express metod mos kelmasa 404 beradi — 405 + Allow ni o'zimiz
  // qo'yamiz. @All() — shu yo'ldagi qolgan hamma metod; marshrutlar
  // e'lon tartibida ro'yxatdan o'tadi — aniq metodlar oldin
  @All()
  toplamMetodYoq(@Req() req: Request): never {
    throw new MetodYoqXatosi(req.method, "GET, HEAD, POST, PUT");
  }

  /** Bulk yaratish — qisman muvaffaqiyat: 207 Multi-Status
   * (/:id dan OLDIN — aks holda "toplam" id deb olinadi) */
  @Post("toplam")
  async toplam(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ natijalar: Natija[] }> {
    jsonTanaKerak(req);
    const body = tekshir(BulkSxemasi, req.body, "body");
    const tekshirilgan = body.vazifalar.map(
      (el) => YangiVazifaSxemasi.safeParse(el),
    );
    const yaroqli = tekshirilgan.flatMap(
      (t) => (t.success ? [t.data] : []),
    );
    const yaratilgan = await this.xizmat.toplamYarat(yaroqli);
    let k = 0;
    const natijalar = tekshirilgan.map((t, indeks): Natija => {
      if (!t.success) {
        const xabar = t.error.issues.map((i) => i.message).join("; ");
        const joy = `body.vazifalar[${String(indeks)}]`;
        return { indeks, holat: 422, xato: { joy, xabar } };
      }
      const vazifa = yaratilgan[k++];
      return vazifa === undefined
        ? { indeks, holat: 500 }
        : { indeks, holat: 201, vazifa };
    });
    res.status(natijalar.every((n) => n.holat === 201) ? 201 : 207);
    return { natijalar };
  }

  @All("toplam")
  toplamPostYoq(@Req() req: Request): never {
    throw new MetodYoqXatosi(req.method, "POST");
  }

  @Get(":id")
  async ol(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<Vazifa> {
    const params = tekshir(IdSxemasi, req.params, "params");
    const v = await this.xizmat.ol(params.id);
    vazifaSarlavhalari(res, v);
    keshYoq(res);
    return v;
  }

  /** PUT — to'liq almashtirish (idempotent: 2 marta = 1 marta) */
  @Put(":id")
  async almashtir(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<Vazifa> {
    jsonTanaKerak(req);
    const params = tekshir(IdSxemasi, req.params, "params");
    const body = tekshir(ToliqVazifaSxemasi, req.body, "body");
    const v = await this.xizmat.almashtir(
      params.id, body, ifMatch(req),
    );
    vazifaSarlavhalari(res, v);
    return v;
  }

  /** PATCH — qisman (JSON Merge Patch). If-Match: "3" — faqat
   * 3-versiya bo'lsa yoz */
  @Patch(":id")
  async yangila(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<Vazifa> {
    jsonTanaKerak(req);
    const params = tekshir(IdSxemasi, req.params, "params");
    const body = tekshir(OzgarishSxemasi, req.body, "body");
    const v = await this.xizmat.yangila(
      params.id, body, ifMatch(req),
    );
    vazifaSarlavhalari(res, v);
    return v;
  }

  @Delete(":id")
  @HttpCode(204)
  async ochir(@Req() req: Request): Promise<void> {
    const params = tekshir(IdSxemasi, req.params, "params");
    await this.xizmat.ochir(params.id, ifMatch(req));
  }

  @All(":id")
  birMetodYoq(@Req() req: Request): never {
    throw new MetodYoqXatosi(
      req.method, "GET, HEAD, PUT, PATCH, DELETE",
    );
  }
}

src/vazifalar/teglar.controller.ts:

ts
// teglar.controller.ts — /v1/teglar: teglar va nechta vazifada
import { All, Controller, Get, Req, Res } from "@nestjs/common";
import type { Request, Response } from "express";
import { MetodYoqXatosi } from "../umumiy/xatolar.ts";
import { keshYoq } from "../umumiy/shartli.ts";
import type { TegSoni } from "./ombor.ts";
import { VazifalarService } from "./vazifalar.service.ts";

@Controller("teglar")
export class TeglarController {
  constructor(private readonly xizmat: VazifalarService) {}

  @Get()
  async teglar(
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ teglar: TegSoni[] }> {
    keshYoq(res);
    return { teglar: await this.xizmat.teglar() };
  }

  @All()
  metodYoq(@Req() req: Request): never {
    throw new MetodYoqXatosi(req.method, "GET, HEAD");
  }
}

src/eksport/eksport.controller.ts:

ts
// eksport.controller.ts — POST → 202 Accepted + Location (holat
// resursi), mijoz so'rab turadi (polling), tayyor bo'lsa — fayl
import {
  Controller, Get, HttpCode, Post, Req, Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { IlovaXatosi } from "../umumiy/xatolar.ts";
import { tekshir } from "../umumiy/tekshir.ts";
import { asosYol } from "../umumiy/yol.ts";
import { EksportSxemasi } from "../vazifalar/sxema.ts";
import { EksportService } from "./eksport.service.ts";

@Controller("eksportlar")
export class EksportController {
  constructor(private readonly eksport: EksportService) {}

  @Post()
  @HttpCode(202)
  boshla(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Record<string, unknown> {
    const body = tekshir(EksportSxemasi, req.body, "body");
    const ish = this.eksport.boshla(body);
    const asos = asosYol(req, "eksportlar");
    res.location(`${asos}/${ish.id}`).set("Retry-After", "1");
    return this.eksport.korinish(ish, asos);
  }

  @Get(":id")
  holat(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Record<string, unknown> {
    const ish = this.eksport.topilsin(String(req.params["id"]));
    if (ish.holat === "kutilmoqda" || ish.holat === "bajarilmoqda") {
      res.set("Retry-After", "1");
    }
    res.set("Cache-Control", "no-store");
    return this.eksport.korinish(ish, asosYol(req, "eksportlar"));
  }

  @Get(":id/fayl")
  fayl(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): string {
    const ish = this.eksport.topilsin(String(req.params["id"]));
    if (ish.natija === undefined) {
      throw new IlovaXatosi(409, "Eksport hali tayyor emas", {
        kod: "eksport-tayyor-emas",
        sarlavha: "Eksport tayyor emas",
        httpSarlavhalar: { "Retry-After": "1" },
      });
    }
    const kengaytma = ish.sorov.format;
    const sana = ish.yaratildi.slice(0, 10);
    res.attachment(`vazifalar-${sana}.${kengaytma}`).type(
      kengaytma === "json"
        ? "application/json; charset=utf-8"
        : "text/csv; charset=utf-8",
    );
    // satr — Nest res.send() bilan yuboradi, Content-Type o'zgarmaydi
    return ish.natija;
  }
}

Eksport controller'ida ikki yangi narsa: @HttpCode(202) — ish qabul qilindi, keyin bajariladi; fayl metodi satr qaytaradi — Nest uni res.send() bilan yuboradi va res.type(...) qo'ygan Content-Type ni o'zgartirmaydi.

12.5 Modullarga ulash

ts
// vazifalar.module.ts — vazifalar xususiyat moduli:
// controller'lar + xizmat
import { Module } from "@nestjs/common";
import { VazifalarController } from "./vazifalar.controller.ts";
import { TeglarController } from "./teglar.controller.ts";

@Module({
  controllers: [VazifalarController, TeglarController],
  // providers va exports — keyingi darsda
})
export class VazifalarModule {}

EksportModule ga — controllers: [EksportController].

12.6 Tekshiruv: bitta kutilgan xato

Controller'lar VazifalarService va EksportService ga tayanadi — ular keyingi darsda yoziladi. Shuning uchun hozir npm run tip xato beradi. Biz shu holatni kanon nusxasida tekshirdik:

text
src/eksport/eksport.controller.ts(11,32): error TS2307: Cannot find module './eksport.service.ts' or its corresponding type declarations.
src/vazifalar/teglar.controller.ts(7,34): error TS2307: Cannot find module './vazifalar.service.ts' or its corresponding type declarations.
src/vazifalar/vazifalar.controller.ts(27,34): error TS2307: Cannot find module './vazifalar.service.ts' or its corresponding type declarations.
src/vazifalar/vazifalar.controller.ts(94,10): error TS7006: Parameter 'v' implicitly has an 'any' type.

Tarjimasi: TS2307 — "./vazifalar.service.ts modulini yoki uning tur e'lonlarini topib bo'lmadi". TS7006 — "v parametrining turi yashirin any". To'rtinchi xato birinchisining oqibati: xizmat yo'q bo'lgani uchun royxat() natijasining turi noma'lum, map((v) => …) dagi v ham.

Faqat shu to'rt xato bo'lsa — hammasi to'g'ri. Boshqa xato chiqsa (masalan, TS2305 — import nomi xato), uni hozir tuzating. Keyingi darsda xizmatlar yozilgach tip toza bo'ladi. Shuning uchun bu darsda npm test va build ni yurgizmaymiz va hali commit qilmang — commit butun API ko'chgandan keyin, Bootstrap: main.ts darsida.

13. Real ishda

  • Har Nest loyihasida controller — eng ko'p yoziladigan fayl. Jamoalarda qoida: controller yupqa — faqat HTTP; mantiq xizmatda. 200 qatorli controller metodi — review'da birinchi savol.
  • @Res() — real loyihalarda fayl va oqim (stream) uchun ishlatiladi. Review'da "bu yerda passthrough kerak emasmi?" savoli tez-tez beriladi.
  • Marshrut tartibi — /users/me va /users/:id klassik misol: me ni tepaga yozish unutilsa, me id deb olinadi.
  • Intervyu: "@Res() ishlatganda nima o'zgaradi?", "Nest POST uchun qaysi status qaytaradi?", "@Param qanday turda keladi?".

Xulosa

  • @Controller("prefiks") + @Get/@Post/...("yo'l") — marshrutlar; Nest ularni klassdagi tartibda ro'yxatga oladi.
  • @Param, @Query, @Body, @Headers, @Ip, @Req — so'rov qismlari; hammasi tekshirilmagan, yo'l va query doim satr.
  • return — javob tanasi; POST 201, qolgani 200; @HttpCode, @Header, @Redirect — dekorator bilan boshqarish.
  • @Res() — javob sizning zimmangizda, return e'tiborsiz; passthrough: true — sarlavha sizda, tana Nest'da.
  • Aniq yo'llar parametrlilardan oldin; 405 — @All() bilan, aniq metodlardan keyin.
  • vazifalar-api controller'lari yozildi; tip faqat xizmat yo'qligi haqida xato beradi — keyingi darsda yo'qoladi.

Keyingi dars: Provider'lar va service'lar — @Injectable xizmat klasslarini yozamiz, 25-qismdagi xizmat.ts mantig'i ularga ko'chadi va controller'lar ishga tushadi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS controller'lari: @Controller, @Get, @Param, @Body va @Res tuzog'i — IlmHamroh