Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Multipart tanasi ichida nima bor
- 2.1 O'z ko'zingiz bilan
- 2.2 Qatorma-qator
- 3. Multer bilan birinchi marshrut
- 3.1 Nega Express o'zi o'qimaydi
- 3.2 single va diskStorage
- 3.3 array, fields, none
- 4. Limitlar va fileFilter
- 4.1 limits — majburiy
- 4.2 fileFilter — qaysi fayllar qabul qilinadi
- 5. Multer xatolarini javobga aylantirish
- 5.1 Ishlovchisiz nima bo'ladi
- 5.2 To'rt argumentli ishlovchi
- 6. Disk yoki xotira?
- 6.1 memoryStorage — xotira o'lchovi
- 6.2 Kanon topgan tuzoq: Date.now() nomi
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): multipart tanani qo'lda o'qish
- 3-mashq (qiyin): limitli marshrut
- 10. Loyiha qadami: kanon tajribasi, loyihaga hali kirmaydi
- 11. Real ishda
- Xulosa
- Manbalar
Fayl yuklash Express'da: multipart/form-data va Multer
Qisqacha: Brauzer faylni
multipart/form-dataformatida yuboradi: tana boundary (ajratgich) bilan qismlarga bo'linadi, har qismda maydon nomi, fayl nomi va baytlar. Express bu formatni o'zi o'qimaydi — uni Multer middleware'i o'qiydi:upload.single("rasm")faylnireq.filega, oddiy maydonlarnireq.bodyga qo'yadi. Har yuklash marshrutidalimits(hajm, son) va Multer xatolarini 400/413 ga aylantiradigan xato ishlovchi majburiy.
Bu darsda
multipart/form-datatanasini o'z ko'zingiz bilan ko'rib, qismlarini o'qiy olasiz.- Multer bilan bitta, bir nechta va turli maydonlardagi fayllarni qabul qilasiz (
single,array,fields,none). limitsvafileFilterqo'yib, Multer xatolarini to'g'ri status kodga aylantirasiz.diskStoragevamemoryStoragefarqini o'lchov bilan tushuntirasiz.- Kanon topgan tuzoqni bilasiz:
Date.now()bilan fayl nomi bir faylni ikkinchisining ustiga yozadi.
Oldin bilishingiz kerak: Fayl yuklash (upload) va progress (brauzer tomoni, FormData), Request body o'qish va hajm cheklovi, Mashhur middleware'lar, Global error handler.
28-qismda foydalanuvchi tizimga kirishi va huquqlar bilan ishladik. Endi yangi bo'lim — backend integratsiyalar: fayllar, email, Telegram bot, to'lovlar va real vaqt. Birinchi qadam — fayl qabul qilish.
Maslahat: Bu qismda Node'ga xos kod (
express,multer,node:fs) saytdagi «Ishga tushir» tugmasida ishlamaydi — u brauzerda bajariladi. Bunday bloklarni o'z kompyuteringizda, alohida tajriba papkasida yurgizasiz. Brauzerda ham ishlaydigan toza funksiyalar esa jonli.Darsda kanon so'zi ko'p uchraydi. Kanon — bu qism uchun biz oldindan yozib, ishga tushirib, chiqishini yozib olgan namunaviy kod va tajribalar. Darsdagi server javoblari va o'lchovlar o'sha yozuvlardan olingan, o'ylab topilmagan.
1. Nega bu kerak?
vazifalar-api hozir faqat JSON qabul qiladi. Request body darsida forma yuborganda u 415 qaytargan edi. Lekin Malika vazifaga ilova qo'shmoqchi: "Ta'til rejasi" vazifasiga mehmonxona chekining rasmi.
Faylni JSON ichida yuborsa bo'lmaydimi? Bo'ladi, lekin yomon. JSON — matn. Rasm esa baytlar. Uni matnga aylantirish (base64) hajmni taxminan uchdan biriga oshiradi, server esa butun JSON'ni xotiraga yig'ib, keyin ochadi. 50 MB lik video uchun bu halokat.
Shuning uchun brauzer fayllarni boshqa formatda yuboradi — multipart/form-data. U Fayl yuklash maydoni darsidagi enctype va Fayl yuklash darsidagi FormData ning o'zi. Siz uni brauzer tomondan bilasiz. Bu darsda server tomonini ko'ramiz.
O'xshatish: multipart — bitta qutiga solingan bir nechta konvert. Har konvert ustida yorliq ("bu — sarlavha", "bu — rasm, nomi chek.png"), ichida esa narsaning o'zi. Konvertlar orasida bir xil chiziq bor — u boundary.
2. Multipart tanasi ichida nima bor
2.1 O'z ko'zingiz bilan
Brauzer FormData dan tanani qanday yasashini ko'raylik. Request obyekti (Built-in fetch va undici) tanani so'rov yubormasdan matnga aylantirib beradi. Boundary har safar tasodifiy — o'qish oson bo'lsin deb uni <B> bilan, qator oxiridagi ikki belgini (CR va LF) esa ␍␊ belgisi bilan almashtiramiz:
const fd = new FormData();
fd.append("sarlavha", "Ta'til rejasi");
fd.append(
"rasm",
new File(["salom dunyo\n"], "eslatma.txt", { type: "text/plain" }),
);
const req = new Request("https://example.com/yuklash", {
method: "POST",
body: fd,
});
const tur = req.headers.get("content-type");
const boundary = tur.split("boundary=")[1];
const tana = await req.text();
console.log(tur.replace(boundary, "<B>"));
const korinish = tana.replaceAll(boundary, "<B>");
console.log(korinish.replaceAll("\r\n", "␍␊\n"));Konsolda:
multipart/form-data; boundary=<B>
--<B>␍␊
Content-Disposition: form-data; name="sarlavha"␍␊
␍␊
Ta'til rejasi␍␊
--<B>␍␊
Content-Disposition: form-data; name="rasm"; filename="eslatma.txt"␍␊
Content-Type: text/plain␍␊
␍␊
salom dunyo
␍␊
--<B>--␍␊Kanon xuddi shu tanani haqiqiy Express serveriga yuborib, server ko'rganini yozib olgan. U yerda boundary ----formdata-undici-036794743845 edi — Node'ning fetch i shunday nom beradi. Chrome esa ----WebKitFormBoundary… yozadi. Tuzilma bir xil.
2.2 Qatorma-qator
boundary=<B>— ajratgich so'rov sarlavhasida e'lon qilinadi. Shuning uchunContent-Typeni qo'lda yozish taqiqlangan (Fayl yuklash darsidagi qoida).--<B>— har qism ikki chiziqcha + boundary bilan boshlanadi.Content-Disposition: form-data; name="sarlavha"— qism sarlavhasi: qaysi maydon.- Bo'sh qator (
␍␊yolg'iz) — sarlavhalar tugadi, keyin qiymat. - Fayl qismida qo'shimcha
filename="eslatma.txt"vaContent-Type: text/plain. --<B>--— oxirgi chiziq: oxirida yana ikki chiziqcha.
E'tibor bering: filename va qism ichidagi Content-Type ni mijoz yozgan. Server ularni tekshira olmaydi — ular shunchaki matn. Bu darsning oxirida, Fayl yuklash xavfsizligi darsida esa to'liq kerak bo'ladi.
Tekshirib ko'ring: Fayl ichida tasodifan
--<B>ga o'xshash qator bo'lsa-chi? Server faylni noto'g'ri bo'lib qo'ymaydimi?
Javob
Shuning uchun boundary uzun va tasodifiy (----formdata-undici-036794743845). Brauzer uni har so'rovda yangidan o'ylab topadi — fayl ichida aynan shu ketma-ketlik uchrashi deyarli imkonsiz. --<B> faqat bizning namoyishimiz uchun qisqa yozuv.
3. Multer bilan birinchi marshrut
3.1 Nega Express o'zi o'qimaydi
Mashhur middleware'lar darsida express.json() va express.urlencoded() ni ko'rgansiz. Ikkalasi tanani to'liq o'qib, req.body ga qo'yadi. Multipart uchun Express'da o'rnatilgan middleware yo'q: fayl katta bo'lishi mumkin, uni qayerga qo'yishni (disk, xotira, bulut) ilova hal qiladi.
Multer — Express jamoasi qo'llab-quvvatlaydigan middleware. U ichida busboy kutubxonasini ishlatadi: busboy tanani oqim sifatida o'qib, qismlarga ajratadi (Stream nima). Multer esa qismlarni diskka yoki xotiraga yozib, req.file va req.body ni to'ldiradi.
sequenceDiagram
participant B as Brauzer
participant M as Multer (busboy)
participant D as Disk yoki xotira
participant R as Marshrut
participant X as Xato ishlovchi
B->>M: multipart tana (oqim)
M->>D: fayl baytlari
alt limitlar ichida
M->>R: req.file + req.body
R-->>B: 200 JSON
else limit oshdi
M->>D: chala faylni o'chiradi
M->>X: MulterError
X-->>B: 400 yoki 413
endDiagrammaga qarang: marshrut funksiyangiz faqat hamma narsa yaxshi bo'lganda chaqiriladi. Xato bo'lsa, Multer marshrutni chetlab o'tadi va xatoni to'g'ridan xato ishlovchiga beradi.
npm i multer@2.4.0Kanon versiyalari: multer 2.4.0, Express 5.3.0 (Node 24.21.0). Multer 2.0.0 2025-yil 19-mayda chiqqan (npm reliz sanasi), 1.x dagi xizmatni to'xtatish (DoS) zaifliklari tuzatilgan. Eski darslarda multer@1.4.5-lts ni ko'rsangiz — o'sha davr kodi, yangilang.
3.2 single va diskStorage
Kanon tajribasidan qisqartirilgan server (telefon uchun qatorlarni bo'ldik):
import express from "express";
import multer from "multer";
const upload = multer({
storage: multer.diskStorage({
destination: papka,
filename: (_req, file, cb) =>
cb(null, `${Date.now()}-${file.fieldname}`),
}),
limits: { fileSize: 1024 * 1024, files: 3, fields: 5 },
});
const app = express();
app.post("/single", upload.single("rasm"), (req, res) =>
res.json({ file: req.file, body: req.body }),
);upload.single("rasm") — marshrutdagi middleware. U rasm nomli maydondagi bitta faylni kutadi. Middleware tugaganda fayl allaqachon diskda, siz marshrut ichida faqat natijani ishlatasiz.
Kanon sarlavha: "x" maydoni va 3 baytli a.txt faylni yubordi. Javob (o'qish oson bo'lsin deb qatorlarga bo'ldik; vaqtinchalik papka yo'lini … bilan qisqartirdik):
200 {
"file": {
"fieldname": "rasm",
"originalname": "a.txt",
"encoding": "7bit",
"mimetype": "text/plain",
"path": "C:\\Users\\…\\Temp\\t01-0sLSo1\\1793108334164-rasm",
"destination": "C:\\Users\\…\\Temp\\t01-0sLSo1",
"filename": "1793108334164-rasm",
"size": 3
},
"body": { "sarlavha": "x" }
}| Maydon | Ma'nosi | Kim yozgan |
|---|---|---|
fieldname |
forma maydoni nomi | mijoz |
originalname |
foydalanuvchi kompyuteridagi nom | mijoz |
mimetype |
fayl turi (qism Content-Type i) |
mijoz |
filename, path |
diskdagi nom va to'liq yo'l | server |
size |
haqiqiy baytlar soni | server |
Jadvalga qarang: yuqoridagi uchtasi — mijoz aytgan narsa, ishonib bo'lmaydi. Pastdagilari — server o'zi hisoblagan.
memoryStorage da path, destination, filename bo'lmaydi — o'rniga buffer (faylning baytlari, Buffer).
3.3 array, fields, none
| Usul | Nima kutadi | Natija |
|---|---|---|
single("rasm") |
rasm da 1 ta fayl |
req.file |
array("rasmlar", 2) |
rasmlar da 2 tagacha |
req.files — massiv |
fields([...]) |
bir nechta nomli maydon | req.files — obyekt |
none() |
faqat matn maydonlari | fayl kelsa — xato |
any() |
istalgan maydonda istalgancha | req.files — massiv |
Kanon fields ni shunday berdi:
app.post(
"/fields",
upload.fields([
{ name: "avatar", maxCount: 1 },
{ name: "hujjat", maxCount: 2 },
]),
(req, res) => res.json({ maydonlar: sanab(req.files) }),
);Bitta avatar va ikkita hujjat yuborilganda: 200 {"maydonlar":{"avatar":1,"hujjat":2}}. req.files.avatar — bitta elementli massiv, req.files.hujjat — ikki elementli.
array("rasmlar", 2) ga kanon avval 2 ta, keyin 3 ta fayl yubordi:
200 {"soni":2,"nomlar":["1.txt","2.txt"]}
400 {"code":"LIMIT_UNEXPECTED_FILE","field":"rasmlar","message":"Unexpected file field"}Kutilmagan natija: uchinchi fayl uchun Multer LIMIT_FILE_COUNT emas, LIMIT_UNEXPECTED_FILE berdi. Sabab: maxCount oshganda Multer o'sha maydonni "kutilmagan" deb hisoblaydi. LIMIT_FILE_COUNT esa limits.files (butun so'rovdagi jami fayllar) oshganda chiqadi. Xato xabarini foydalanuvchiga ko'rsatishda buni hisobga oling.
any() dan qoching: u har qanday maydon nomini qabul qiladi, siz kutmagan fayllar ham diskka tushadi.
Tekshirib ko'ring: Forma
<input type="file" name="rasm" multiple>dan 3 ta rasm yubordi, server esaupload.single("rasm"). Nima bo'ladi?
Javob
Birinchi fayl qabul qilinadi, ikkinchisida Multer LIMIT_UNEXPECTED_FILE xatosini beradi (single — maxCount: 1 li maydon). Xato bo'lgani uchun Multer allaqachon yozilgan birinchi faylni ham diskdan o'chiradi. Bir nechta fayl kerak bo'lsa — array("rasm", 3).
4. Limitlar va fileFilter
4.1 limits — majburiy
Limitsiz Multer istalgan hajmdagi faylni qabul qiladi. Hujumchi 50 GB yuborsa — disk to'ladi. Request body darsida JSON tanaga limit qo'ygandik — fayl uchun ham xuddi shu g'oya.
| Limit | Nimani cheklaydi | Oshsa |
|---|---|---|
fileSize |
bitta fayl hajmi (bayt) | LIMIT_FILE_SIZE |
files |
so'rovdagi fayllar soni | LIMIT_FILE_COUNT |
fields |
matn maydonlari soni | LIMIT_FIELD_COUNT |
fieldSize |
bitta matn maydoni hajmi | LIMIT_FIELD_VALUE |
parts |
jami qismlar (fayl + maydon) | LIMIT_PART_COUNT |
Kanon limitlari fileSize: 1 MB, files: 3, fields: 5. Natijalar:
1 MB + 1 bayt: 413 {"code":"LIMIT_FILE_SIZE","field":"rasm","message":"File too large"}
6 ta maydon: 400 {"code":"LIMIT_FIELD_COUNT","message":"Too many fields"}
kutilmagan maydon: 400 {"code":"LIMIT_UNEXPECTED_FILE","field":"fayl","message":"Unexpected file field"}1 MB dan bitta bayt ortiq fayl ham rad etildi. Hajm oshgan zahoti Multer o'qishni to'xtatadi va diskdagi chala faylni o'chiradi.
4.2 fileFilter — qaysi fayllar qabul qilinadi
fileFilter(req, file, cb) har fayl uchun, baytlari o'qilishidan oldin chaqiriladi. U faqat sarlavhani ko'radi: fieldname, originalname, mimetype. Uch javob mumkin:
cb(null, true)— qabul.cb(null, false)— jim o'tkazib yuborish: fayl tashlanadi, so'rov davom etadi,req.file—undefined.cb(xato)— so'rov xato bilan to'xtaydi.
Biz ikkinchi variantni ishga tushirdik (Node 24.21.0, multer 2.4.0): text/plain faylga cb(null, false) — javob 200 {"fayl":null,"body":{}}. Foydalanuvchi "yuklandi" deb o'ylaydi, fayl esa yo'q. Shuning uchun aniq xabar bilan rad etish yaxshiroq. Kanon shunday qildi:
fileFilter: (_req, file, cb) => {
const ruxsat = ["image/png", "image/jpeg", "text/plain"];
if (ruxsat.includes(file.mimetype)) cb(null, true);
else {
cb(new multer.MulterError(
"LIMIT_UNEXPECTED_FILE", file.fieldname,
));
}
},PDF yuborilganda: 400 {"code":"LIMIT_UNEXPECTED_FILE",…}. Ishladi. Endi hujumchi kabi o'ylaymiz. Kanon exe faylning baytlarini (MZ bilan boshlanadi) olib, rasm.png nomi va image/png turi bilan yubordi:
yolg'on MIME (exe → image/png): 200 {"file":{"fieldname":"rasm","originalname":"rasm.png","encoding":"7bit","mimetype":"image/png",…,"size":12},"body":{}}O'tdi. fileFilter mijoz yozgan mimetype ga qaradi, u esa yolg'on edi. fileFilter — foydalanuvchiga qulaylik (noto'g'ri faylni tez rad etish), lekin himoya emas. Haqiqiy turni faylning birinchi baytlaridan aniqlash — keyingi darsda.
Diqqat: Kengaytma (
.png) ham,mimetypeham — mijoz yozgan matn. Ularga qarab "bu rasm" deb qaror qilmang.
5. Multer xatolarini javobga aylantirish
5.1 Ishlovchisiz nima bo'ladi
Xato ishlovchi bo'lmasa, Multer xatosi Express'ning sukut ishlovchisiga tushadi. Biz ishga tushirdik (development rejimi): boshqa maydon nomi bilan fayl → 500 va javob tanasida to'liq stack trace — MulterError: Unexpected file field va serverdagi papkalar yo'li. Boundary'siz Content-Type → 500 Error: Multipart: Boundary not found.
Ikkalasi ham mijoz xatosi, 500 esa "server buzildi" degani (Status kodlarni to'g'ri tanlash). Ustiga ichki yo'llar ko'rinib qoladi.
5.2 To'rt argumentli ishlovchi
Global error handler darsidagi to'rt argumentli middleware Multer xatolarini ham ushlaydi. MulterError klassi bor — instanceof bilan ajratamiz. Avval qaysi kod qaysi statusga borishini toza funksiya qilib yozamiz (brauzerda ham ishlaydi):
const XABARLAR = {
LIMIT_FILE_SIZE: [413, "Fayl juda katta"],
LIMIT_FILE_COUNT: [400, "Fayllar soni ko'p"],
LIMIT_UNEXPECTED_FILE: [400, "Bu maydonda fayl kutilmagan"],
LIMIT_FIELD_COUNT: [400, "Maydonlar soni ko'p"],
LIMIT_PART_COUNT: [400, "Qismlar soni ko'p"],
};
function yuklashXatosi(kod, maydon) {
const [status, xabar] = XABARLAR[kod] ?? [400, "Yuklashda xato"];
return { status, xabar, maydon: maydon ?? null };
}
console.log(yuklashXatosi("LIMIT_FILE_SIZE", "rasm"));
console.log(yuklashXatosi("LIMIT_UNEXPECTED_FILE", "fayl"));
console.log(yuklashXatosi("BOSHQA"));Konsolda:
{ status: 413, xabar: 'Fayl juda katta', maydon: 'rasm' }
{ status: 400, xabar: 'Bu maydonda fayl kutilmagan', maydon: 'fayl' }
{ status: 400, xabar: 'Yuklashda xato', maydon: null }413 Content Too Large — tana server qabul qiladiganidan katta. Qolganlari — 400. Endi Express'ga ulaymiz:
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
const x = yuklashXatosi(err.code, err.field);
return res.status(x.status).json(x);
}
next(err); // boshqa xatolar — umumiy ishlovchiga
});vazifalar-api da xatolar Problem Details formatida (Xato javoblari) — u yerda json(x) o'rniga o'sha formatni yasaysiz. Boundary'siz so'rov (Multipart: Boundary not found) MulterError emas, oddiy Error — uni ham 400 qiling (busboy xatosi).
6. Disk yoki xotira?
6.1 memoryStorage — xotira o'lchovi
memoryStorage faylni to'liq Buffer qilib xotiraga yig'adi. Rasmni darhol qayta ishlash (masalan, sharp bilan kichraytirish) uchun qulay. Lekin narxi bor. Kanon 50 MB fayl yubordi va jarayon xotirasini (RSS) o'lchadi:
bufer 50.0 MB, RSS 82.5 MB → 370.1 MB (bitta so'rov; klient bufer ham shu jarayonda)Bitta 50 MB yuklash jarayon xotirasini ~288 MB ga oshirdi. Halol izoh: o'lchovda yuboruvchi fetch ham shu jarayonda edi va u ham 50 MB ni xotirada ushlagan. Shunga qaramay xulosa aniq: o'nta parallel yuklash — gigabaytlar. Keyingi darslarda 200 MB fayl bilan alohida server jarayonida o'lchaymiz (Katta fayllarni stream bilan yuklash).
diskStorage |
memoryStorage |
|
|---|---|---|
| Fayl qayerda | vaqtinchalik papkada | req.file.buffer da |
| Xotira | kichik | fayl hajmicha (va undan ko'p) |
| Qachon | katta fayl, keyin omborga | kichik rasm, darhol qayta ishlash |
| Majburiy | nomni o'zingiz bering | qattiq fileSize limiti |
6.2 Kanon topgan tuzoq: Date.now() nomi
Kanon serverida filename — ${Date.now()}-${file.fieldname}. Ko'p darslik va blog shunday yozadi. Kanon muvaffaqiyatli yuklashlarni sanadi: single 1 + array 2 + fields 3 + yolg'on MIME 1 = 7 ta fayl. Diskda esa:
<vaqt>-rasm 3 bayt
<vaqt>-rasmlar 1 bayt
<vaqt>-avatar 1 bayt
<vaqt>-hujjat 1 bayt
<vaqt>-hujjat 1 bayt
<vaqt>-rasm 12 bayt
diskda 6 ta — `Date.now()-fieldname` nomi bir millisekundda to'qnashsa fayl USTIGA yoziladi; xato bo'lgan so'rov fayllari Multer tomonidan o'chirildi7 ta o'rniga 6 ta. array dagi ikki fayl bir millisekundda yozildi, ikkalasi …-rasmlar nomini oldi — ikkinchisi birinchisining ustiga yozildi. Hech qanday xato chiqmadi. Bu deterministik emas: boshqa safar 7 ta ham bo'lishi mumkin. Shuning uchun test ham uni doim ushlamaydi.
Disk o'rniga Map bilan bir xil holatni ko'raylik (Map — xuddi papka: bir nom — bitta fayl):
const disk = new Map();
const vaqt = 1793108334164; // ikki fayl bir millisekundda keldi
disk.set(`${vaqt}-rasmlar`, "1.txt ning baytlari");
disk.set(`${vaqt}-rasmlar`, "2.txt ning baytlari");
console.log(disk.size, [...disk.values()]);
const toza = new Map();
for (const nom of ["1.txt", "2.txt"]) {
toza.set(`${crypto.randomUUID()}.txt`, `${nom} ning baytlari`);
}
console.log(toza.size);Konsolda:
1 [ '2.txt ning baytlari' ]
2Birinchi faylning baytlari yo'qoldi. crypto.randomUUID() (crypto: tasodif, hash va HMAC) har chaqiruvda yangi, 122 bitli tasodifiy nom beradi — to'qnashuv amalda imkonsiz. To'g'ri filename:
import { randomUUID } from "node:crypto";
filename: (_req, _file, cb) => cb(null, randomUUID()),Kengaytmani originalname dan olmang — u ham mijoz matni. Haqiqiy turga qarab kengaytma berishni keyingi darsda qilamiz.
Tekshirib ko'ring: Nega
filename: (req, file, cb) => cb(null, file.originalname)eng xavfli variant?
Javob
Ikki sabab. Birinchisi — ikki foydalanuvchi chek.png yuborsa, biri ikkinchisining faylini o'chiradi. Ikkinchisi — nomda ../../ bo'lishi mumkin: path traversal. Multer sukutda nomdan yo'l qismini olib tashlaydi, lekin bitta sozlama bu himoyani o'chiradi — kanon buni keyingi darsda ko'rsatadi.
7. Hujumchi nigohi
Fayl yuklash marshruti — serverga begona baytlar yoziladigan eshik. Hujumchi nima qiladi:
- Diskni to'ldiradi.
limitssiz bitta 50 GB so'rov yoki minglab kichik so'rov. Himoya —fileSize,files,fields,partsva bir foydalanuvchidan keladigan so'rovlar sonini cheklash (Rate limiting). - Xotirani tugatadi.
memoryStorage+ katta limit + parallel so'rovlar. Himoya — kichik limit yoki disk/oqim. - Yolg'on tur yuboradi.
fileFilterga ishonch — exeimage/pngbo'lib o'tdi. Himoya — sehrli baytlar (Fayl yuklash xavfsizligi). - Nom bilan o'ynaydi.
../../, juda uzun nom, ko'rinmas belgilar. Himoya — server o'zi tasodifiy nom beradi. - Ichki ma'lumotni ko'radi. Ishlovchisiz Multer xatosi — 500 va stack trace. Himoya — «Multer xatolarini javobga aylantirish» bo'limidagi xato ishlovchi.
Kanon tajribasidagi limitlar va xato ishlovchi birinchi, ikkinchi va beshinchisini yopdi. Uchinchi va to'rtinchisi — keyingi dars.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
req.file — undefined |
maydon nomi mos emas yoki fileFilter false qaytardi |
nomlarni solishtiring; rad etishda xato bering |
500 Error: Multipart: Boundary not found |
mijoz Content-Type ni qo'lda yozgan |
FormData da sarlavhani yozmang; xatoni 400 qiling |
MulterError: Unexpected file field |
single ga bir nechta fayl, boshqa maydon nomi yoki maxCount oshdi |
array/fields va to'g'ri nom |
req.body — undefined |
so'rov JSON edi, Multer uni o'tkazib yubordi | Multer faqat multipart'ni o'qiydi; JSON — express.json() |
| Diskda fayllar kam | Date.now() nomi to'qnashdi |
randomUUID() |
| Server xotirasi to'ldi | memoryStorage + katta fayllar |
diskStorage yoki oqim |
Oxirgisidan oldingisi — biz ishga tushirdik: JSON so'rovga upload.single("rasm") jim o'tdi, req.file ham req.body ham bo'sh qoldi. Multer o'zi xato bermaydi.
9. Mashqlar
Mashqlar kurs/mashqlar/29/01-multer/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Multipart tanada qismlar orasidagi ajratgich —
upload.single("rasm")faylnireq.ga qo'yadi.- Fayl hajmi limitdan oshsa, Multer xato kodi —
- Unga mos HTTP status kod —
fileFilterqaraydiganmimetypeni yozgan.
Yechim
- boundary — u
Content-Typesarlavhasida e'lon qilinadi. - file — bir nechta faylda
req.files. - LIMIT_FILE_SIZE —
limits.fileSizeoshdi. - 413 — Content Too Large.
- mijoz — shuning uchun
fileFilterhimoya emas, qulaylik.
2-mashq (o'rta): multipart tanani qo'lda o'qish
qismlarniOl(tana, boundary) funksiyasini yozing. U tanani qismlarga bo'lib, har biri uchun { nom, faylNomi, qiymat } qaytarsin (faylNomi bo'lmasa — null). Pastdagi namunaviy tana bilan sinang. Ishora: tanani "--" + boundary bo'yicha split qiling (satr metodlari); har qismda sarlavha va qiymat birinchi "\r\n\r\n" bilan ajraladi; oxirgi qism "--" bilan boshlanadi.
const B = "----formdata-undici-036794743845";
const tana =
`--${B}\r\n` +
`Content-Disposition: form-data; name="sarlavha"\r\n\r\n` +
`Ta'til rejasi\r\n` +
`--${B}\r\n` +
`Content-Disposition: form-data; name="rasm"; ` +
`filename="eslatma.txt"\r\n` +
`Content-Type: text/plain\r\n\r\n` +
`salom dunyo\n\r\n` +
`--${B}--\r\n`;Yechim
const B = "----formdata-undici-036794743845";
const tana =
`--${B}\r\n` +
`Content-Disposition: form-data; name="sarlavha"\r\n\r\n` +
`Ta'til rejasi\r\n` +
`--${B}\r\n` +
`Content-Disposition: form-data; name="rasm"; ` +
`filename="eslatma.txt"\r\n` +
`Content-Type: text/plain\r\n\r\n` +
`salom dunyo\n\r\n` +
`--${B}--\r\n`;
function qismlarniOl(matn, boundary) {
const natija = [];
for (const qism of matn.split(`--${boundary}`)) {
if (qism === "" || qism.startsWith("--")) continue;
const i = qism.indexOf("\r\n\r\n");
const sarlavha = qism.slice(0, i);
// oxirgi \r\n — ajratgichniki
const qiymat = qism.slice(i + 4, -2);
const nom = sarlavha.match(/ name="([^"]*)"/)?.[1];
const faylNomi = sarlavha.match(/filename="([^"]*)"/)?.[1];
natija.push({ nom, faylNomi: faylNomi ?? null, qiymat });
}
return natija;
}
console.log(qismlarniOl(tana, B));Konsolda:
[
{ nom: 'sarlavha', faylNomi: null, qiymat: "Ta'til rejasi" },
{ nom: 'rasm', faylNomi: 'eslatma.txt', qiymat: 'salom dunyo\n' }
]slice(i + 4, -2): boshidagi to'rt belgi — bo'sh qator (\r\n\r\n), oxiridagi ikki belgi — keyingi ajratgichdan oldingi \r\n. Fayl ichidagi \n esa qiymatning o'zi — u saqlandi. name= dan oldingi bo'shliq filename= bilan adashmaslik uchun. Bu o'quv parseri: haqiqiy busboy tanani oqim sifatida o'qiydi, butun tanani xotiraga yig'maydi va qochirilgan qo'shtirnoqlar kabi chekka holatlarni biladi. Ishlab chiqarishda o'zingiz parser yozmang.
3-mashq (qiyin): limitli marshrut
Tajriba papkasida (npm i express@5.3.0 multer@2.4.0) server yozing: POST /chek — chek maydonida bitta fayl, xotirada, fileSize 1 000 bayt, faqat image/png va image/jpeg (boshqasi — MulterError("LIMIT_UNEXPECTED_FILE")). Xatolar darsdagi yuklashXatosi funksiyasi bilan javobga aylansin, muvaffaqiyatda { nom, hajm }. Server listen(0) da ochilib, o'zi to'rt so'rov yuborsin: 10 baytli PNG, 1 001 baytli PNG, PDF, boshqa maydonidagi PNG. Har javobni status tana ko'rinishida chiqarib, serverni yoping.
Yechim
import express from "express";
import multer from "multer";
import { once } from "node:events";
const XABARLAR = {
LIMIT_FILE_SIZE: [413, "Fayl juda katta"],
LIMIT_UNEXPECTED_FILE: [400, "Bu maydonda fayl kutilmagan"],
};
function yuklashXatosi(kod, maydon) {
const [status, xabar] = XABARLAR[kod] ?? [400, "Yuklashda xato"];
return { status, xabar, maydon: maydon ?? null };
}
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 1000, files: 1 },
fileFilter: (_req, file, cb) => {
if (["image/png", "image/jpeg"].includes(file.mimetype)) {
cb(null, true);
} else {
cb(new multer.MulterError(
"LIMIT_UNEXPECTED_FILE", file.fieldname,
));
}
},
});
const app = express();
app.post("/chek", upload.single("chek"), (req, res) => {
res.json({ nom: req.file.originalname, hajm: req.file.size });
});
app.use((err, _req, res, next) => {
if (!(err instanceof multer.MulterError)) return next(err);
const x = yuklashXatosi(err.code, err.field);
res.status(x.status).json(x);
});
const server = app.listen(0, "127.0.0.1");
await once(server, "listening");
const url = `http://127.0.0.1:${server.address().port}/chek`;
async function yubor(maydon, hajm, tur, nom) {
const fd = new FormData();
const blob = new Blob([new Uint8Array(hajm)], { type: tur });
fd.append(maydon, blob, nom);
const r = await fetch(url, { method: "POST", body: fd });
console.log(r.status, await r.text());
}
await yubor("chek", 10, "image/png", "chek.png");
await yubor("chek", 1001, "image/png", "katta.png");
await yubor("chek", 10, "application/pdf", "chek.pdf");
await yubor("boshqa", 10, "image/png", "chek.png");
server.close();Konsolda (biz ishga tushirdik: Node 24.21.0, express 5.3.0, multer 2.4.0):
200 {"nom":"chek.png","hajm":10}
413 {"status":413,"xabar":"Fayl juda katta","maydon":"chek"}
400 {"status":400,"xabar":"Bu maydonda fayl kutilmagan","maydon":"chek"}
400 {"status":400,"xabar":"Bu maydonda fayl kutilmagan","maydon":"boshqa"}Uchinchi va to'rtinchi javob bir xil xabar beradi, lekin sababi boshqa: biri — tur, biri — maydon nomi. Real loyihada fileFilter uchun alohida kod (yaroqsiz-tur) bergan ma'qul — foydalanuvchi nima qilishni tushunsin. blob ichidagi nollar PNG emasligini sezdingizmi? fileFilter buni bilmaydi — u faqat type: "image/png" ni ko'radi.
10. Loyiha qadami: kanon tajribasi, loyihaga hali kirmaydi
Bu darsda vazifalar-api ga kod qo'shilmaydi. Kanon Multer'ni faqat tajribada sinadi va loyiha uchun boshqa yo'lni tanladi: faylni busboy bilan oqim sifatida o'qib, diskka ham, xotiraga ham yig'masdan omborga yozish. Sabablari keyingi uch darsda o'lchov bilan chiqadi: Multer memoryStorage 200 MB faylda server xotirasini +405 MB oshirdi, diskStorage esa fayllarni avval server diskiga, keyin omborga ikki marta yozadi.
O'zingiz sinab ko'ring: alohida papkada npm i express@5.3.0 multer@2.4.0, 3-mashq serverini yurgizing. Keyin filename ni Date.now() bilan almashtirib, array ga 5 ta kichik fayl yuboring va diskdagi fayllarni sanang.
11. Real ishda
- Avatar, chek, hujjat yuklash — deyarli har veb-ilovada. Kichik loyihalarda Multer + disk ko'p uchraydi; kattalarida fayl to'g'ridan omborga (Object storage: S3 va MinIO, Presigned URL'lar).
- NestJS —
FileInterceptorichida aynan Multer ishlaydi (Fayl yuklash NestJS'da). Bu darsdagi hamma narsa u yerda ham amal qiladi. - Kod review'da —
limitsyo'qligi,fileFilterga ishonish vaoriginalnamebilan saqlash eng ko'p topiladigan xatolar. - Intervyuda: "multipart/form-data nima va nega JSON emas?", "Multer'da disk va memory storage farqi?", "Fayl yuklashda qanday limitlar qo'yasiz?"
Xulosa
- Fayl
multipart/form-datada keladi: boundary bilan bo'lingan qismlar, har biridaContent-Dispositionva qiymat. - Multer — Express uchun multipart middleware:
single→req.file,array/fields→req.files, matn →req.body. limitsmajburiy;maxCountoshsa xato kodiLIMIT_UNEXPECTED_FILE,LIMIT_FILE_COUNTemas.fileFiltervamimetype— mijoz matni: exeimage/pngbo'lib o'tdi.- Xato ishlovchisiz Multer xatosi 500 va stack trace; ishlovchi bilan — 400/413.
Date.now()nomi faylni jim o'chiradi —randomUUID();memoryStorage— faqat kichik fayllarga.
Keyingi dars: Fayl yuklash xavfsizligi — faylning haqiqiy turini sehrli baytlardan aniqlaymiz, nomni tozalaymiz va SVG ichidagi skript haqiqiy brauzerda qanday ishlashini ko'ramiz.
Manbalar
- Multer (README:
single,array,fields,limits,fileFilter,MulterError): https://github.com/expressjs/multer - Multer xavfsizlik e'lonlari (2.0.0 versiyasidagi tuzatishlar): https://github.com/expressjs/multer/security/advisories
- busboy (limitlar va hodisalar): https://github.com/mscdex/busboy
- RFC 7578 —
multipart/form-data: https://www.rfc-editor.org/rfc/rfc7578 - RFC 9110 §15.5.14 — 413 Content Too Large: https://www.rfc-editor.org/rfc/rfc9110#name-413-content-too-large
- Kanon tajribasi (IlmHamroh, 2026-10-27): multer 2.4.0, Express 5.3.0, Node 24.21.0, Windows 11.
fileFilterfalse, ishlovchisiz xatolar va mashq yechimlari — biz ishga tushirdik (2026-10-28, o'sha versiyalar).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!