Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. FileInterceptor — Multer Nest'da
- 2.1 Birinchi yuklash
- 2.2 Fayl qachon o'qiladi
- 2.3 Bir nechta fayl
- 2.4 Xotira yoki disk
- 3. ParseFilePipe va validatorlar
- 3.1 Haqiqiy xabarlar
- 3.2 Chalkash xabarlar
- 3.3 FileTypeValidator ichida
- 3.4 ParseFilePipeBuilder
- 4. StreamableFile — faylni berish
- 5. O'lchov: Multer xotirasi
- 5.1 Tajriba
- 5.2 O'qish
- 6. vazifalar-api da: oqim @Req() orqali
- 6.1 Controller
- 6.2 Jonli natija
- 7. Kanon topgan xatolar
- 7.1 POST sukuti 201 — @Res() bilan ham
- 7.2 Inglizcha sarlavha — kichik nomuvofiqlik
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): kim rad etadi?
- 2-mashq (o'rta): oqimda hajm chegarasi
- 3-mashq (qiyin): sarlavhasiz xatoni ushlash
- 11. Loyiha qadami: fayl ilovalari Nest'da
- 12. Real ishda
- Xulosa
- Manbalar
Fayl yuklash NestJS'da: FileInterceptor, ParseFilePipe va oqimli yuklash
Qisqacha: Nest'da fayl yuklashning standart yo'li — Multer:
@UseInterceptors(FileInterceptor("fayl"))va@UploadedFile(new ParseFilePipe({ validators: [...] })).MaxFileSizeValidatorhajmni,FileTypeValidatoresa faylning birinchi baytlarini (magic bytes) tekshiradi. Faylni qaytarish —StreamableFile. Multer sukut bo'yicha faylni to'liq xotiraga yig'adi: biz o'lchaganda 100 MB fayl server xotirasini ~200 MB ga oshirdi, oqim bilan o'qish esa ~50 MB. Shuning uchunvazifalar-api29-qismdagi oqimli yuklashni (busboy → S3) saqladi va Nest'da uni@Req()orqali ishlatadi.
Bu darsda
FileInterceptor,FilesInterceptorva@UploadedFilebilan fayl qabul qilasiz.ParseFilePipevalidatorlarining haqiqiy xabarlarini vaFileTypeValidatortuzoqlarini bilasiz.StreamableFilebilan faylni yuklab berasiz.- Multer va oqimli yuklashning xotira narxini o'lchov bilan solishtirasiz.
vazifalar-apining fayl controller'ini va kanon ko'chishda topgan ikki xatoni ko'rasiz.
Oldin bilishingiz kerak: Fayl yuklash: multipart va Multer, Fayl yuklash xavfsizligi, Katta fayllarni stream bilan yuklash, Pipe'lar, Interceptor'lar va RxJS.
Auth endi loyihada: 28-qismdagi sessiya, rollar va RLS Nest'ga ko'chdi, har so'rov egasi bilan keladi. Endi 29-qismdagi integratsiyalar navbati — birinchisi fayllar.
1. Nega bu kerak?
29-qismda vazifalar-api ga fayl ilovalari qo'shilgandi: vazifaga chizma, chek yoki PDF biriktirish (Fayl yuklash: multipart va Multer). U yerda ikki yo'lni ko'rgandik: Multer (oddiy, faylni yig'adi) va oqim (busboy → S3, xotiraga yig'maydi). Kanon oqimni tanlagandi.
Nest'da fayl yuklashning o'z "rasmiy" yo'li bor — u Multer ustiga qurilgan. Nest loyihasiga ishga kirsangiz, 90 % holatda FileInterceptor ni ko'rasiz. Shuning uchun uni to'liq o'rganamiz. Keyin o'lchaymiz va vazifalar-api nega boshqa yo'ldan borganini ko'ramiz.
O'xshatish: pochta bo'limiga katta posilka keldi. Birinchi usul — posilkani to'liq omborga olib kirib, keyin tekshirish va jo'natish. Ikkinchi usul — konveyer: quti kirishi bilan tekshiriladi va to'g'ri yuk mashinasiga o'tadi. Kichik posilkalarda farq yo'q. Yuz kilogrammlik yuklar kunda yuzlab kelsa — ombor to'ladi.
2. FileInterceptor — Multer Nest'da
2.1 Birinchi yuklash
Multer @nestjs/platform-express ichida keladi — alohida o'rnatish shart emas (biz o'rnatganda multer 2.4.0 keldi). Tip uchun @types/multer kerak. Demo controller (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0):
const MB = 1024 * 1024;
@Controller()
class FaylController {
@Post("rasm")
@UseInterceptors(FileInterceptor("fayl", {
limits: { fileSize: 5 * MB, files: 1 },
}))
rasm(
@UploadedFile(new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 1 * MB }),
new FileTypeValidator({ fileType: "image/png" }),
],
}))
fayl: Express.Multer.File,
) {
return { nom: fayl.originalname, hajm: fayl.size,
mime: fayl.mimetype };
}
}Uch qism:
FileInterceptor("fayl", sozlama)— interceptor (Interceptor'lar va RxJS): handler'dan oldin Multer'ni chaqiradi va multipart tanadanfaylnomli maydonni o'qiydi. Ikkinchi argument — Multer sozlamalari:limits,storage,fileFilter.@UploadedFile()— parametr dekoratori,req.fileni beradi. Ichiga pipe qo'yish mumkin.Express.Multer.File— fayl obyekti turi:originalname,mimetype,size,buffer(xotirada) yokipath(diskda).
limits ni unutmang. Busiz Multer istalgan hajmdagi faylni qabul qiladi — 29-qismdagi DoS xavfi (Fayl yuklash xavfsizligi).
2.2 Fayl qachon o'qiladi
FileInterceptor — interceptor (ushlab qoluvchi). Demak u so'rov hayot sikli dagi o'z joyida ishlaydi. Fayl baytlari aynan shu joyda o'qiladi:
| Qadam | Kim | Fayl bilan nima bo'ladi |
|---|---|---|
| 1 | middleware (express.json) |
multipart/form-data ni tanimaydi — tegmaydi |
| 2 | guard (KirishGuard, CheklovGuard) |
tana hali o'qilmagan; rad etsa — Multer umuman ishga tushmaydi |
| 3 | interceptor (FileInterceptor) |
Multer tanani o'qiydi, limits shu yerda — 413 |
| 4 | pipe (ParseFilePipe) |
tayyor fayl obyektini tekshiradi — 400 |
| 5 | handler | fayl.buffer bilan ishlaydi |
Bu tartib xavfsizlik uchun foydali. Anonim so'rov yoki limitdan oshgan foydalanuvchi 2-qadamda to'xtaydi — server uning 100 MB faylini xotiraga yig'ib o'tirmaydi. Agar faylni middleware'da o'qisangiz (masalan, app.use(multer().any())), hamma yuklash guard'dan oldin xotiraga tushardi: anonim ham serverni to'ldira olardi.
Tekshirib ko'ring: Validatorlar pipe'da ishlaydi. Unda nega Multer
limitsbaribir kerak?
Javob
Pipe 4-qadamda, fayl to'liq o'qilgandan keyin ishlaydi. 10 GB fayl yuborilsa, MaxFileSizeValidator uni ko'rguncha xotira allaqachon to'lgan bo'ladi. limits.fileSize esa 3-qadamda, o'qish paytida to'xtatadi. Biri xotirani himoya qiladi, ikkinchisi — biznes qoidasini.
2.3 Bir nechta fayl
| Interceptor | Dekorator | Nima |
|---|---|---|
FileInterceptor("fayl") |
@UploadedFile() |
bitta maydon, bitta fayl |
FilesInterceptor("fayllar", 2) |
@UploadedFiles() |
bitta maydon, ko'pi bilan 2 ta |
FileFieldsInterceptor([{ name, maxCount }]) |
@UploadedFiles() |
bir nechta nomli maydon |
AnyFilesInterceptor() |
@UploadedFiles() |
istalgan maydon (ehtiyot bo'ling) |
Global sozlama — MulterModule.register({ dest, limits }) modulda. Har interceptor'da sozlama berilsa, u ustun.
2.4 Xotira yoki disk
Multer'da ikki saqlash usuli bor:
memoryStorage(sukut) — faylBuffersifatidafayl.bufferda. Oson, lekin butun fayl RAM'da.diskStorage— fayl vaqtinchalik papkaga yoziladi,fayl.pathberiladi. RAM tejaladi, lekin disk ishi va keyin o'chirish kerak.
FileInterceptor("fayl", { storage: diskStorage({ destination, filename }) }). filename da foydalanuvchi bergan originalname ni to'g'ridan ishlatmang — path traversal xavfi (Fayl yuklash xavfsizligi).
3. ParseFilePipe va validatorlar
3.1 Haqiqiy xabarlar
Demo controller'ga oltita so'rov yubordik:
png: 201 {"nom":"chizma.png","hajm":16,"mime":"image/png"}
soxta png: 400 {"message":"Validation failed (current file type is image/png, expected type is image/png)","error":"Bad Request","statusCode":400}
2 MB: 400 {"message":"Validation failed (current file size is 2097152, expected size is less than 1048576)","error":"Bad Request","statusCode":400}
6 MB: 413 {"message":"File too large","error":"Payload Too Large","statusCode":413}
faylsiz: 400 {"message":"File is required","error":"Bad Request","statusCode":400}
3 ta fayl: 400 {"message":"Unexpected file field - fayllar","error":"Bad Request","statusCode":400}"Soxta png" — rasm.png nomli, image/png turli, lekin ichida "salom" matni. Qatorma-qator:
| So'rov | Kim rad etdi | Status |
|---|---|---|
| haqiqiy PNG | — | 201 |
| soxta PNG | FileTypeValidator (magic bytes) |
400 |
| 2 MB | MaxFileSizeValidator (1 MB) |
400 |
| 6 MB | Multer limits.fileSize (5 MB) |
413 |
| fayl yo'q | ParseFilePipe (sukut fileIsRequired: true) |
400 |
| 3 ta fayl (chegara 2) | Multer (FilesInterceptor ning maxCount) |
400 |
Ikki xil chegara bor: Multer limits — o'qish paytida, ortiqcha baytlar o'qilmaydi (413). MaxFileSizeValidator — fayl to'liq o'qilgandan keyin (400). Xavfsizlik uchun birinchisi muhim: u xotirani himoya qiladi. Ikkinchisi — chiroyli xabar va biznes qoidasi uchun.
3.2 Chalkash xabarlar
Ikki xabarni sekin o'qiymiz:
- "Validation failed (current file type is image/png, expected type is image/png)" — "tekshiruv o'tmadi (joriy tur image/png, kutilgan tur image/png)". Ikkala tur bir xil — nega xato? Chunki validator mimetype'ni emas, baytlarni tekshirdi va PNG imzosini topmadi. Xabar esa mijoz yuborgan mimetype'ni ko'rsatadi. Bunday xabarni ko'rsangiz — fayl ichini tekshiring.
- "Unexpected file field - fayllar" — "kutilmagan fayl maydoni". Maydon nomi to'g'ri, lekin fayllar soni
maxCountdan oshdi. Multer bu holatni "kutilmagan maydon" deb ataydi (LIMIT_UNEXPECTED_FILE).
3.3 FileTypeValidator ichida
Nest 12.2.0 manba kodiga qaradik: FileTypeValidator file-type paketi (22.1.1) bilan fayl.buffer ning birinchi baytlarini o'qiydi. Bu 29-qismdagi magic bytes tekshiruvi (Fayl yuklash xavfsizligi). Sozlamalari:
| Sozlama | Nima qiladi |
|---|---|
fileType |
kutilgan tur — satr yoki RegExp |
skipMagicNumbersValidation |
baytlarni tekshirmaydi, faqat mimetype (xavfli) |
fallbackToMimetype |
imzo topilmasa — mimetype'ga ishonadi |
overrideMimeType |
aniqlangan haqiqiy turni fayl.mimetype ga yozadi |
Ikki tuzoq kodda yozilgan:
diskStoragebilanfayl.bufferyo'q. Validator baytlarni o'qiy olmaydi va sukut bo'yicha rad etadi: "file buffer is not available; file type validation could not be performed".- CSV, TXT kabi matn fayllarida imzo yo'q.
file-typeularni tanimaydi va validator rad etadi.fallbackToMimetype: trueularni o'tkazadi — lekin soxta PNG'ni ham.
Buni kichik modelda ko'ramiz (brauzerda ishlaydi):
// FileTypeValidator modeli: tur — mijoz aytgan mimetype'dan emas,
// faylning birinchi baytlaridan (magic bytes) aniqlanadi
const IMZOLAR = [
{ mime: "image/png", bosh: [0x89, 0x50, 0x4e, 0x47] },
{ mime: "image/jpeg", bosh: [0xff, 0xd8, 0xff] },
{ mime: "application/pdf", bosh: [0x25, 0x50, 0x44, 0x46] },
];
function turniAniqla(baytlar) {
const mos = (i) => i.bosh.every((b, n) => baytlar[n] === b);
return IMZOLAR.find(mos)?.mime;
}
function tekshir(fayl, kutilgan, sozlama = {}) {
const { fallbackToMimetype = false } = sozlama;
const haqiqiy = turniAniqla(fayl.baytlar);
if (haqiqiy !== undefined) return haqiqiy === kutilgan;
return fallbackToMimetype && fayl.mimetype === kutilgan;
}
const matn = (s) => [...new TextEncoder().encode(s)];
const fayllar = [
{ nom: "chizma.png", mimetype: "image/png",
baytlar: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a] },
{ nom: "rasm.png", mimetype: "image/png", baytlar: matn("salom") },
{ nom: "royxat.csv", mimetype: "text/csv",
baytlar: matn("id,matn") },
];
for (const f of fayllar) {
const kutilgan = f.nom.endsWith(".csv") ? "text/csv" : "image/png";
const qattiq = tekshir(f, kutilgan);
const yumshoq = tekshir(f, kutilgan, { fallbackToMimetype: true });
console.log(f.nom, "→", turniAniqla(f.baytlar) ?? "yo'q",
"| qattiq:", qattiq, "| fallback:", yumshoq);
}Konsolda:
chizma.png → image/png | qattiq: true | fallback: true
rasm.png → yo'q | qattiq: false | fallback: true
royxat.csv → yo'q | qattiq: false | fallback: trueIkkinchi qator — xavf: fallbackToMimetype soxta PNG'ni o'tkazib yubordi. CSV kabi imzosiz turlar uchun fallback kerak bo'lsa, rasm uchun alohida, qattiq validator yozing. Model faqat uch imzoni biladi; file-type yuzlabini.
Tekshirib ko'ring: Validatorlar tartibi
[MaxFileSizeValidator, FileTypeValidator]. 6 MB soxta PNG yuborildi, Multerlimits.fileSize— 5 MB. Qaysi xabar keladi?
Javob
413 "File too large". Multer limitdan oshgan faylni o'qish paytida to'xtatadi — handler va ParseFilePipe umuman ishlamaydi. Validatorlar faqat Multer o'tkazgan faylni ko'radi.
3.4 ParseFilePipeBuilder
Xuddi shu sozlamaning zanjirli shakli ham bor:
@UploadedFile(
new ParseFilePipeBuilder()
.addFileTypeValidator({ fileType: "image/png" })
.addMaxSizeValidator({ maxSize: 1 * MB })
.build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }),
)
fayl: Express.Multer.File,errorHttpStatusCode — xato statusi (sukut 400). vazifalar-api validatsiya xatolariga 422 beradi; Multer yo'li bilan yozsak, shu sozlama kerak bo'lardi. Bu blokni ishga tushirmadik — sintaksisi Nest hujjatidan.
4. StreamableFile — faylni berish
Fayl qaytarish uchun @Res() shart emas. StreamableFile — oqim yoki Buffer ni o'raydigan klass. Nest uni ko'rib, javobni oqim bilan yuboradi:
@Get("yukla")
ber() {
return new StreamableFile(Readable.from(["id,matn\n1,Non\n"]), {
type: "text/csv; charset=utf-8",
disposition: 'attachment; filename="vazifalar.csv"',
});
}StreamableFile: 200 attachment; filename="vazifalar.csv" "id,matn\n1,Non\n"Nima bo'ldi? Nest qaytarilgan obyekt StreamableFile ekanini ko'rdi: type dan Content-Type ni, disposition dan Content-Disposition ni qo'ydi, keyin oqimni javobga uladi. attachment so'zi brauzerga "faylni ochma, saqla" deydi — 29-qismdagi XSS himoyasining bir qismi. Handler @Res() ni umuman olmadi, javob yuborishni Nest o'zi qildi.
Afzalligi: interceptor va filtrlar ishlaydi, @Res() tuzog'i yo'q (Controller'lar va route dekoratorlari). Kamchiligi: Range so'rovlari (206, videoni o'rtasidan ochish) va ETag'ni o'zingiz qilishingiz kerak. Express res.sendFile ularni o'zi qiladi.
5. O'lchov: Multer xotirasi
5.1 Tajriba
Serverni alohida jarayonda ishga tushirdik, mijoz — curl (biz ishga tushirdik, bitta mashina, bitta o'lchov). Ikki endpoint: /multer — FileInterceptor (memoryStorage), /oqim — busboy bilan oqim (baytlar sanaladi, xotiraga yig'ilmaydi). Har so'rov oldidan gc() va server RSS'i (jarayon egallagan RAM) o'lchandi, so'rov paytida har 5 ms da eng katta RSS yozildi. Fayl — 100 MB. Chiqishni juft-juft o'qing: usul qatori — server javobi, undan keyingi engKatta qatori — shu so'rov paytidagi o'lchov. Eng birinchi qator — hech narsa yuklanmagandagi boshlang'ich holat:
{"engKatta":90,"gcDanKeyin":91}
{"usul":"oqim","hajm":104857600}
{"engKatta":140,"gcDanKeyin":91}
{"usul":"multer","hajm":104857600}
{"engKatta":293,"gcDanKeyin":292}- boshlang'ich91 MB
- oqim (busboy), eng katta+49 MB, gc'dan keyin 91140 MB
- Multer, eng katta+202 MB, gc'dan keyin 292293 MB
Manba: Biz ishga tushirdik (2026-11-02): NestJS 12.2.0, multer 2.4.0, busboy 1.6.0, Node 24.21.0, bitta mashina, bitta o'lchov
5.2 O'qish
- Multer 100 MB fayl uchun ~200 MB qo'shdi — fayldan ham ko'p. Multer kelgan bo'laklarni yig'ib, oxirida bitta
Bufferga birlashtiradi: bir lahza ikkala nusxa ham xotirada. - gc'dan keyin ham 292 MB. JS obyektlari bo'shadi, lekin jarayon xotirani operatsion tizimga qaytarmadi. Keyingi so'rovlar shu joyni qayta ishlatadi, lekin konteyner xotira chegarasi (masalan, 512 MB) shu "cho'qqi" bo'yicha ishlaydi.
- Oqim ~50 MB vaqtinchalik bo'laklar bilan o'tdi va gc'dan keyin boshlang'ich holatga qaytdi.
Endi tasavvur qiling: to'rtta foydalanuvchi bir vaqtda 100 MB video yuklaydi. Multer bilan — ~800 MB, oqim bilan — bir necha o'n MB. 29-qismdagi Express o'lchovi ham shu tomonga ko'rsatgandi (Katta fayllarni stream bilan yuklash).
Tekshirib ko'ring:
diskStoragexotira muammosini hal qiladimi? Negavazifalar-apibaribir undan foydalanmaydi?
Javob
RAM muammosini hal qiladi — fayl diskka yoziladi. Lekin vazifalar-api fayllarni S3 (MinIO) ga saqlaydi. diskStorage bilan fayl avval diskka, keyin diskdan S3 ga ketadi: ikki marta yozish, vaqtinchalik fayllarni tozalash, disk to'lishi xavfi. Oqim esa baytlarni to'g'ridan S3 multipart yuklashiga uzatadi. Ustiga, diskStorage bilan FileTypeValidator sukut bo'yicha ishlamaydi — buffer yo'q.
6. vazifalar-api da: oqim @Req() orqali
6.1 Controller
29-qismdagi Express routeri Nest controller'iga ko'chdi; SQL, S3 kodi va javoblar aynan qoldi. Yuklash handler'i (qisqartirilgan parcha, qatorlar bo'lingan):
@Controller("vazifalar/:vazifaId")
@Cheklov(API_CHEKLOVI)
export class IlovalarController {
constructor(
@Inject(FAYLLAR) private readonly b: FaylBogliqliklari,
) {}
@Post("ilovalar")
async yukla(
@Egasi() egasi: number,
@Req() req: Request,
@Res() res: Response,
): Promise<void> {
const b = this.b;
const vazifaId = idOl(req, "vazifaId");
// Yuklashdan OLDIN: vazifa yo'q bo'lsa baytlarni S3 ga
// yozmaymiz (aks holda yetim obyekt)
await vazifaBormi(b.pool, egasi, vazifaId);
const n = await oqimliYuklash(req, {
ombor: b.ombor,
maksHajm: b.maksHajm,
kalit: (k) => saqlashKaliti(egasi, k, "fayllar"),
}).catch(xatoniAylantir);
// … bazaga yozish (RLS ostida), dublikat bo'lsa yangi obyektni
// o'chirish, 201 + Location
}
// … GET ro'yxat, GET ilovalar.zip, GET :faylId (Range), DELETE
}Nest'ga xos to'rt narsa:
@Req()— xom Express so'rovi.oqimliYuklash(29-qismdan, o'zgarmagan)reqni busboy'ga uzatadi va fayl bo'laklari S3 multipart yuklashiga oqadi.FileInterceptoryo'q — u tanani o'zi o'qib qo'yardi.@Res()— javobni o'zimiz yuboramiz: Range (206), ZIP oqimi vaLocation. Bularres.status().location().json()va 29-qismdagifaylniBer/zipBerfunksiyalari.@Egasi()— kanonning param dekoratori:req.egasini beradi, yo'q bo'lsa 401 (Custom dekoratorlar). Anonim so'rov bu yerga yetmaydi — globalKirishGuarduni oldinroq 401 bilan qaytaradi.@Cheklov(API_CHEKLOVI)— 28-qismdagi so'rov limiti, endi guard.
vazifaBormi — vazifa egasiga tegishlimi, yuklashdan oldin tekshiradi. Aks holda begona vazifaga 100 MB yuklanib, keyin rad etilardi — S3 da yetim fayl qolardi.
6.2 Jonli natija
Kanon tekshiruvidan (haqiqiy MinIO, sessiya cookie'si bilan; token yashirilgan, mahalliy yo'l qisqartirildi, Permissions-Policy va ETag qatorlari olib tashlandi):
$ curl -H Cookie: __Host-sessiya=<token> -F fayl=@C:/Users/…/.ilova.png;filename=chizma.png;type=application/octet-stream http://127.0.0.1:4552/v1/vazifalar/9/ilovalar
HTTP/1.1 201 Created
Location: /v1/vazifalar/9/ilovalar/1
Content-Type: application/json; charset=utf-8
Content-Length: 80
{"faylId":1,"aslNom":"chizma.png","mime":"image/png","hajm":70,"dublikat":false}
$ curl -H Cookie: __Host-sessiya=<token> -H Range: bytes=0-7 -o NUL -D - http://127.0.0.1:4552/v1/vazifalar/9/ilovalar/1
HTTP/1.1 206 Partial Content
Content-Type: image/png
Content-Disposition: attachment; filename="chizma.png"; filename*=UTF-8''chizma.png
Accept-Ranges: bytes
Content-Range: bytes 0-7/70
Content-Length: 8Birinchi so'rovga qarang: mijoz type=application/octet-stream yubordi, javobda esa "mime":"image/png". Server turni baytlardan aniqladi — mijoz aytganiga ishonmadi. Ikkinchisi — Range: 70 baytdan faqat 0–7 berildi (206).
7. Kanon topgan xatolar
7.1 POST sukuti 201 — @Res() bilan ham
Brauzer to'g'ridan S3 ga yuklagandan keyin faylni tasdiqlash yo'li bor: POST /v1/fayllar/:faylId/tasdiq. 29-qismda u 200 qaytarardi — Express sukuti. Nest'ga ko'chgach, 29-qismning fayl.test testi yiqildi: javob 201.
Sabab: Nest POST handler'lari uchun statusni (201) handler'dan oldin qo'yadi — @Res() bilan javobni o'zingiz yuborsangiz ham. res.json(...) status bermadi, demak oldindan qo'yilgan 201 ketdi. Tuzatish — bitta dekorator:
@Post(":faylId/tasdiq")
// Nest POST sukuti 201 — @Res() bilan ham (status handler'dan OLDIN
// qo'yiladi; 29 da Express sukuti 200 edi — test ushladi)
@HttpCode(200)
async tasdiq(
@Egasi() egasi: number,
@Req() req: Request,
@Res() res: Response,
): Promise<void> {
// … HEAD + Range + magic bytes + sha256 → 'tayyor'
}Saboq: Express'dan Nest'ga ko'chishda statuslar jim o'zgarishi mumkin. Buni faqat status kodni tekshiradigan test ushlaydi — 29-qism testlari shunday edi.
7.2 Inglizcha sarlavha — kichik nomuvofiqlik
Jonli tekshiruvdagi yana bir javob — begona vazifaga yuklash:
{"type":"https://vazifalar-api.example/xatolar/topilmadi","title":"Not Found","status":404,"detail":"Vazifa topilmadi","instance":"/v1/vazifalar/14/ilovalar","kod":"topilmadi","sorovId":"2503258c-e48f-4dd1-ac32-ad50b8399615"}title — "Not Found", inglizcha. API'ning boshqa xatolarida sarlavha o'zbekcha ("Vazifa topilmadi", "Kirish kerak"). Sabab: fayl kodida new TopilmadiXatosi("Vazifa topilmadi", "topilmadi") — uchinchi argument (sarlavha) berilmagan, Problem Details yasovchisi esa Node'ning STATUS_CODES[404] ni oladi. Bu kod 29-qismdan aynan ko'chgan. Xavfsizlikka ta'sir qilmaydi — kod va status to'g'ri — lekin mijoz title ni foydalanuvchiga ko'rsatsa, inglizcha matn chiqadi. Kanonda bu tuzatilmagan; tuzatish — 3-mashq g'oyasida, sarlavhani doim berish.
8. Hujumchi nigohi
Fayl yuklash — eng ko'p hujum qilinadigan joylardan biri. Faqat o'z lokal loyihangizda sinang.
- Ulkan fayl (DoS).
limitssizFileInterceptor— 10 GB fayl xotirani to'ldiradi. Himoya: Multerlimits.fileSizeyoki oqimda bayt hisoblagichi (kanondamaksHajm, 413). - Soxta tur.
.pngnomli HTML yoki SVG — keyin brauzerda ochilsa XSS. Himoya: magic bytes (FileTypeValidatoryoki kanondagiturniAniqla), berishdaContent-Disposition: attachment. - Fayl nomi.
../../etc/passwdyoki boshqarish belgilari. Himoya: saqlash kaliti serverda yasaladi (saqlashKaliti), asl nom faqat tozalanib saqlanadi (faylNomiTozala). 29-qismdagi MulterpreservePathtopilmasini eslang (Fayl yuklash xavfsizligi). - Begona vazifa (BOLA).
POST /v1/vazifalar/<begona id>/ilovalar. Himoya: RLS + har so'rovdaAND egasi_id = $n— jonli natijadagi 404.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
fayl undefined |
maydon nomi mos emas yoki multipart/form-data emas |
FileInterceptor("<mijozdagi nom>") |
| "Unexpected file field" | nom noto'g'ri yoki maxCount oshdi |
nom va sonni tekshiring |
| "current file type is image/png, expected type is image/png" | baytlar PNG emas | fayl ichini tekshiring |
FileTypeValidator CSV'ni rad etadi |
matn faylida imzo yo'q | alohida qoida; ehtiyot bilan fallbackToMimetype |
diskStorage bilan tur tekshiruvi yiqiladi |
buffer yo'q |
o'z tekshiruvingiz yoki memoryStorage + kichik limit |
| POST javobi 201, kutilgani 200 | Nest POST sukuti, @Res() bilan ham |
@HttpCode(200) |
| Anonim so'rov ham katta faylni xotiraga yuklaydi | Multer middleware sifatida ulangan — guard'dan oldin o'qiydi | FileInterceptor — guard'dan keyin ishlaydi |
Oxirgi qatorni Express'dan ko'chgan loyihalarda tez-tez ko'rasiz: app.use(multer(...)) Express'da odatiy edi, Nest'da esa u himoyani chetlab o'tadi.
10. Mashqlar
Mashqlar kurs/mashqlar/30/62-fayl-yuklash/ da.
1-mashq (oson): kim rad etadi?
Multer limits.fileSize: 5 MB, ParseFilePipe da MaxFileSizeValidator({ maxSize: 1 MB }) va FileTypeValidator({ fileType: "image/png" }).
- 3 MB haqiqiy PNG — status
- 8 MB haqiqiy PNG — status
- 100 KB, ichi JPEG, nomi
.png— status - Fayl yuborilmadi (sukut sozlama) — status
Yechim
- 400 — Multer o'tkazdi (5 MB dan kichik),
MaxFileSizeValidator(1 MB) rad etdi. - 413 — Multer o'qish paytida to'xtatdi, validatorlar ishlamadi.
- 400 —
FileTypeValidatorbaytlardan JPEG'ni aniqladi, kutilgani PNG. - 400 —
ParseFilePipesukutifileIsRequired: true: "File is required".
2-mashq (o'rta): oqimda hajm chegarasi
Paketsiz model. oqimdaSana(bolaklar, maks) — bo'laklar massivini (har biri Uint8Array) birma-bir "o'qiydi". Jami bayt maks dan oshishi bilan to'xtab, { status: 413, oqildi } qaytarsin (oqildi — to'xtagungacha o'qilgan bayt). Oshmasa — { status: 201, hajm }. Ikki holatni sinang. Ishora: for...of va break (for...of va for...in).
Yechim
function oqimdaSana(bolaklar, maks) {
let jami = 0;
for (const b of bolaklar) {
jami += b.length;
if (jami > maks) return { status: 413, oqildi: jami };
}
return { status: 201, hajm: jami };
}
const KB = 1024;
const bolak = new Uint8Array(64 * KB);
const kichik = Array.from({ length: 4 }, () => bolak); // 256 KB
const katta = Array.from({ length: 100 }, () => bolak); // 6.25 MB
console.log("kichik:", JSON.stringify(oqimdaSana(kichik, 1024 * KB)));
console.log("katta:", JSON.stringify(oqimdaSana(katta, 1024 * KB)));Konsolda:
kichik: {"status":201,"hajm":262144}
katta: {"status":413,"oqildi":1114112}Katta faylda 6.25 MB dan faqat ~1.06 MB o'qildi — qolgani umuman o'qilmadi. Kanondagi oqimliYuklash va Multer limits.fileSize shu g'oyada ishlaydi. MaxFileSizeValidator esa butun faylni o'qib bo'lgach tekshiradi.
3-mashq (qiyin): sarlavhasiz xatoni ushlash
Paketsiz model. problemYasa(xato) Problem Details obyektini qaytarsin: title — xato.sarlavha, berilmagan bo'lsa — STATUS jadvalidagi inglizcha nom (kanondagidek). tekshirSarlavha(problem) — title inglizcha jadvalda bo'lsa, "ogohlantirish: <status> uchun o'zbekcha sarlavha yo'q" qaytarsin, aks holda "ok". Ikki xato bilan sinang: sarlavhali va sarlavhasiz 404. Ishora: obyekt qiymatlari ro'yxati — Object.values (Obyekt metodlari).
Yechim
const STATUS = {
404: "Not Found", 409: "Conflict", 413: "Payload Too Large",
};
function problemYasa(xato) {
return {
status: xato.status,
title: xato.sarlavha ?? STATUS[xato.status] ?? "Xato",
detail: xato.xabar,
};
}
function tekshirSarlavha(p) {
const inglizcha = Object.values(STATUS).includes(p.title);
return inglizcha
? `ogohlantirish: ${p.status} uchun o'zbekcha sarlavha yo'q`
: "ok";
}
const xatolar = [
{ status: 404, xabar: "Vazifa 14 topilmadi",
sarlavha: "Vazifa topilmadi" },
{ status: 404, xabar: "Vazifa topilmadi" },
];
for (const x of xatolar) {
const p = problemYasa(x);
console.log(p.title, "→", tekshirSarlavha(p));
}Konsolda:
Vazifa topilmadi → ok
Not Found → ogohlantirish: 404 uchun o'zbekcha sarlavha yo'qHaqiqiy loyihada bunday tekshiruvni test sifatida yozish mumkin: hamma xato yo'llarini chaqirib, title inglizcha emasligini tekshirish. Shunda kanondagi nomuvofiqlik darhol ko'rinardi.
11. Loyiha qadami: fayl ilovalari Nest'da
Bu va keyingi uch dars bitta katta qadam: 29-qism integratsiyalari Nest'ga ko'chadi. Commit — WebSocket gateway'lar darsida.
- 29-qismdagi fayl modullari o'zgarishsiz ko'chadi:
src/fayl/yuklash.ts(oqimli yuklash),xavfsizlik.ts(magic bytes, fayl nomi, kalit),s3.ts(FaylOmbori),metama.ts(baza),berish.ts(Range, ZIP). SQL migratsiyalari ham (29-qismdagi201–206). Ularni 29-qism yakuniy commit'ingizdan qaytaring — Passport darsidagi kabi:git checkout <29-qism-commit> -- src/fayl/yuklash.ts. Siz hamonnesttarmog'idasiz; 29-qism commit'i esamainning oxirgi commit'i —git log main --oneline -1uni ko'rsatadi. Auth fayllaridan farqi: bu safarmaindagi eng oxirgi holat kerak, oraliq commit emas. - Yangi fayl —
src/fayl/fayl.controller.ts:IlovalarControllervaFayllarController. Express routeridagi har handler — Nest metodi; ichki kod aynan. «Controller» bo'limidagi naqsh bo'yicha:@Req()oqim,@Res()javob,@Egasi(),@Cheklov(API_CHEKLOVI),tasdiqda@HttpCode(200). - Yangi fayl —
src/umumiy/egasi.ts(@Egasi()dekoratori):
export function egasiOl(req: Request): number {
if (req.egasi === undefined) throw new KirilmaganXatosi();
return req.egasi;
}
export const Egasi = createParamDecorator(
(_: unknown, k: ExecutionContext): number =>
egasiOl(k.switchToHttp().getRequest<Request>()),
);Controller'larni modulga ulash va S3 klientini yaratish — Tashqi xizmatlarni Nest modullariga o'rash darsida (IntegratsiyaModule). Hozircha fayllar kompilyatsiya bo'ladi, lekin marshrutlar hali ulanmagan.
Tekshiruv:
npm run tip
npm run lintTestlar (fayl.test) — MinIO bilan, modul ulangandan keyin. Hali commit qilmang.
12. Real ishda
- Ko'p Nest loyihalarida
FileInterceptor+ memoryStorage + S3'gaPutObject— kichik fayllar (avatar, chek) uchun yetarli va oddiy. Katta fayllarda (video, arxiv) jamoalar presigned URL'ga o'tadi — brauzer to'g'ridan S3 ga yuklaydi (Presigned URL'lar); kanondagiPOST /v1/fayllar/formashu. - Fastify'da
FileInterceptorishlamaydi —@fastify/multipartva o'z ishlovchi (Fastify adapter bilan NestJS). - Intervyu: "Nest'da fayl qanday qabul qilinadi?", "Fayl turini qanday tekshirasiz — mimetype yetarlimi?", "Katta fayllarni qanday yuklaysiz?".
Xulosa
- Nest standarti:
FileInterceptor/FilesInterceptor(Multer) +@UploadedFile(new ParseFilePipe({ validators }));limitsmajburiy. - Multer
limitso'qish paytida to'xtatadi (413), validatorlar o'qilgandan keyin (400);FileTypeValidatormagic bytes'nifile-typebilan tekshiradi. FileTypeValidatorxabari chalkash,diskStoragebilan ishlamaydi,fallbackToMimetypesoxta faylni o'tkazadi.- O'lchov: 100 MB fayl — Multer ~+200 MB RSS, oqim ~+50 MB; kanon oqimni (
@Req()→ busboy → S3) saqladi. - Ko'chish topilmalari: POST sukuti 201
@Res()bilan ham (@HttpCode(200)— tuzatildi), sarlavhasiz 404 — "Not Found" (tuzatilmagan).
Keyingi dars: Navbatlar: @nestjs/bullmq — @Processor va WorkerHost bilan Nest ishchisini yozamiz va kanon nega ishchini Nest'siz qoldirganini o'lchov bilan ko'ramiz.
Manbalar
- NestJS hujjati — File upload (
FileInterceptor,ParseFilePipe,FileTypeValidator): https://docs.nestjs.com/techniques/file-upload - NestJS hujjati — Streaming files (
StreamableFile): https://docs.nestjs.com/techniques/streaming-files - Multer — limits va xato kodlari: https://github.com/expressjs/multer#limits
- Kanon (IlmHamroh, 2026-11-01):
src/fayl/fayl.controller.ts, jonli tekshiruv (MinIO),@HttpCode(200)topilmasi. Validator xabarlari,StreamableFile, xotira o'lchovi vaFileTypeValidatormanba kodi — biz ishga tushirdik/o'qidik (2026-11-02, NestJS 12.2.0, multer 2.4.0, file-type 22.1.1, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!