Mundarija (41)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi controller
- 2.1 Yo'l va metodlar
- 2.2 Marshrutlar jadvali
- 3. So'rovning qismlarini olish
- 3.1 Parametr dekoratorlari
- 3.2 Tajriba: hammasi satr
- 3.3 @Headers() va @Ip()
- 3.4 @Req() — butun so'rov
- 4. Javob: status, sarlavha, yo'naltirish
- 4.1 Qaytarilgan qiymat va sukut status
- 4.2 @HttpCode
- 4.3 @Header
- 4.4 @Redirect
- 5. @Res() tuzog'i
- 5.1 Javobni o'zingiz yuborish
- 5.2 passthrough: true
- 6. Marshrut tartibi tuzog'i
- 7. vazifalar-api ning controller'lari
- 7.1 Express routeri → Nest controller
- 7.2 Token orqali bog'liqlik berish — birinchi uchrashuv
- 7.3 @Req() va @Res({ passthrough: true }) nega
- 7.4 405 — @All()
- 8. Ko'chish natijasi: jonli so'rovlar
- 9. Hujumchi nigohi
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): dekoratorni toping
- 2-mashq (o'rta): marshrutlarni avtomatik tartiblash
- 3-mashq (qiyin): Allow sarlavhasini hisoblash
- 12. Loyiha qadami: controller'lar
- 12.1 Qaysi fayllar
- 12.2 tokenlar.ts va yol.ts
- 12.3 json-kerak.ts
- 12.4 Controller'lar
- 12.5 Modullarga ulash
- 12.6 Tekshiruv: bitta kutilgan xato
- 13. Real ishda
- Xulosa
- Manbalar
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 (@HttpCodebilan 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
@Controllerva 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 vapassthroughni to'g'ri ishlatasiz.vazifalar-apining 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:
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
@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-qismdagiapp.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,@Optionsva 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:
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 +1msMapped {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 JSONDiagrammadagi 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:
@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:
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: YangiVazifadeb 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()
@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:
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
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)metodidaid === 7tekshiruviGET /vazifalar/7da 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-darsdagiHello World!); Promise→ kutadi, keyin natijani yuboradi (asyncmetodlar);undefined→ bo'sh tana.
Tajribadan ikki misol:
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):
@Post('tekshir')
@HttpCode(200)
tekshir(@Body() body: unknown) {
return { ok: true, body };
}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
@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' };
}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/v2Ikkinchi 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:
@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)):
GET /tajriba/res → 200
{"qolda":true}
GET /tajriba/res-unutildi → TimeoutErrorJavob 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:
@Get('passthrough')
passthrough(@Res({ passthrough: true }) res: Response) {
res.set('X-Versiya', '7');
return { nest: 'yubordi' };
}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() resoldi,res.setHeader(...)qildi vareturn csvMatnyozdi. 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:
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:
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:
ol {"id":"toplam"}
toplam {}
ol {"id":"7"}
404Birinchi 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:
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:
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:
/v1/vazifalar
/vazifalar
/v1/vazifalar
/vazifalarVersiyali (/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:
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()
@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:
$ 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 vvares.location(...); status yozilmagan, POST sukuti.207—toplam:iddan oldin turgani uchun yetib bordi;res.status(207)passthrough bilan saqlandi.405+Allow—@All(); Nest'ning o'z404i chiqmadi.400—abcsatr keldi va Zod uni rad etdi (@Paramdoim 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:
- 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: Zodtekshir(); keyin pipe). - Parametr bilan o'ynaydi.
/vazifalar/-1,/vazifalar/1e9,/vazifalar/toplam. Himoya —IdSxemasi(musbat butun son) va to'g'ri marshrut tartibi. - Yo'naltirishdan foydalanadi.
@Redirect+ foydalanuvchi manzili — open redirect. Himoya — ruxsat ro'yxati. - Javobni osiltiradi.
@Res()unutilgan marshrut — har so'rov ulanishni timeout'gacha band qiladi. Ko'p so'rov — server ulanishlari tugaydi. Himoya —passthroughyokireturn. - IP'ni soxtalashtiradi.
X-Forwarded-For: 1.2.3.4—trust proxynoto'g'ri bo'lsa@Ip()yolg'on qiymat beradi va tezlik cheklovi aldanadi. Himoya —trust proxyni 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=uydantegni olish —@("teg")PATCH /vazifalar/5dan5ni olish —@("id")- POST'ning sukut statusini 200 ga o'zgartirish —
@(200) - Javob obyektini olib, tanani Nest'ga qoldirish —
@Res({ : true }) GET /vazifalar/7da@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 yokiNumber()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
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:
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
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:
GET, HEAD, POST, PUT
GET, HEAD, PUT, PATCH, DELETE
nullBirinchi 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
// 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 };// 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):
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:
// 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:
// 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:
// 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
// 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:
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 yerdapassthroughkerak emasmi?" savoli tez-tez beriladi.- Marshrut tartibi —
/users/meva/users/:idklassik misol:meni tepaga yozish unutilsa,meid deb olinadi. - Intervyu: "
@Res()ishlatganda nima o'zgaradi?", "Nest POST uchun qaysi status qaytaradi?", "@Paramqanday 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,returne'tiborsiz;passthrough: true— sarlavha sizda, tana Nest'da.- Aniq yo'llar parametrlilardan oldin; 405 —
@All()bilan, aniq metodlardan keyin. vazifalar-apicontroller'lari yozildi;tipfaqat 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
- NestJS hujjati — Controllers (route dekoratorlari, status, sarlavha, redirect,
@Resvapassthrough): https://docs.nestjs.com/controllers - Express 5 — Routing (marshrut tartibi, yo'l parametrlari): https://expressjs.com/en/guide/routing.html
- RFC 9110 — 405 Method Not Allowed va
Allow: https://www.rfc-editor.org/rfc/rfc9110#name-405-method-not-allowed - OWASP — Unvalidated Redirects and Forwards: https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
- Kanon (IlmHamroh, 2026-10-27):
vazifalar-apicontroller'lari, NestJS 12.2.0, jonli tekshiruv 9/9 × 3 rejim. Tajriba controller'i, ko'chish tugagan holatdagi jonli so'rovlar va oraliq holatdagitscchiqishi — biz ishga tushirdik (2026-11-01, NestJS 12.2.0, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!