Multer — fayl yuklash, storage, filter, size limit

~20 daq · Express · Fayl va Media

Muammodan boshlaylik

Foydalanuvchi profil rasmi yuklashi kerak. express.json() bor — ishlamaydi. express.urlencoded() bor — u ham emas. Nima muammo?

app.post('/upload', (req, res) => {
  console.log(req.body); // ← {} bo'sh!
  console.log(req.file); // ← undefined!
});

Sababi: rasm — JSON emas, matn ham emas. Bu binary data — ikkilik ma'lumot. Brauzer uni multipart/form-data formatida yuboradi. express.json() bu formatni tushunmaydi.

Multer aynan shu formatni qayta ishlaydi.


Multer nima?

Multer — Express uchun multipart/form-data middleware. Fayllarni serverga qabul qilib, saqlaydi va req.file / req.files ga ma'lumot qo'yadi.

npm install multer

Eng oddiy misol

const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// Multer ni sozlash — fayllar qaerga saqlansin
const upload = multer({ dest: 'uploads/' }); // ← uploads/ papkasiga saqlaydi

// Bitta fayl yuklash
app.post('/upload', upload.single('rasm'), (req, res) => {
  //                              ↑ form dagi input name="rasm"
  console.log(req.file); // ← fayl haqida ma'lumot
  res.json({ message: 'Yuklandi', fayl: req.file });
});

req.file qanday ko'rinadi:

{
  "fieldname": "rasm",
  "originalname": "profil.jpg",
  "encoding": "7bit",
  "mimetype": "image/jpeg",
  "destination": "uploads/",
  "filename": "a3f2c1d4e5b6789012345678",
  "path": "uploads/a3f2c1d4e5b6789012345678",
  "size": 245760
}

Muammo: fayl nomi a3f2c1d4e5b6789012345678 — kengaytmasi yo'q. dest ishlatilganda Multer faylni shunday saqlaydi.


Storage — faylni qanday saqlash

Fayl nomini va saqlash joyini boshqarish uchun diskStorage ishlatiladi:

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');       // ← qaerga saqlash (null = xato yo'q)
  },
  filename: (req, file, cb) => {
    // Noyob nom: timestamp + random + asl kengaytma
    const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1e9);
    const ext = path.extname(file.originalname); // ← .jpg, .png, .pdf
    cb(null, uniqueName + ext); // ← masalan: 1717123456789-123456789.jpg
  }
});

const upload = multer({ storage });

Nima uchun asl nom ishlatilmaydi? Chunki ikki foydalanuvchi foto.jpg yuborsa — biri ikkinchisini ustiga yozib ketadi. Noyob nom bu muammoni hal qiladi.


Fayl turi filtri (fileFilter)

Har qanday fayl qabul qilinmasligi kerak. Faqat ruxsat etilgan turlarni o'tkazib yuboring:

const imageFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];

  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);  // ← qabul qil
  } else {
    cb(new Error('Faqat JPEG, PNG, WebP rasmlarga ruxsat'), false); // ← rad et
  }
};

const upload = multer({
  storage,
  fileFilter: imageFilter,
});

Nima uchun kengaytmaga emas, mimetype ga qaraladi? Chunki tajovuzkor zararli.exe ni rasm.jpg deb nomlashi mumkin. mimetype esa fayl ichidagi ma'lumot asosida aniqlanadi — aldab bo'lmaydi.


Hajm cheklovi (size limit)

const upload = multer({
  storage,
  fileFilter: imageFilter,
  limits: {
    fileSize: 5 * 1024 * 1024,  // ← 5 MB (baytda)
    files: 5,                    // ← bir vaqtda maksimal 5 ta fayl
  }
});

To'liq sozlangan misol

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const app = express();

// uploads/ papkasi yo'q bo'lsa yaratish
if (!fs.existsSync('uploads')) {
  fs.mkdirSync('uploads');
}

// Storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1e9);
    const ext = path.extname(file.originalname);
    cb(null, uniqueName + ext);
  }
});

// Filter
const imageFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Faqat rasm fayllarga ruxsat (JPEG, PNG, WebP)'), false);
  }
};

// Upload middleware
const upload = multer({
  storage,
  fileFilter: imageFilter,
  limits: { fileSize: 5 * 1024 * 1024 } // ← 5 MB
});

// Statik fayllar uchun
app.use('/uploads', express.static('uploads')); // ← brauzerdan ko'rish mumkin

// Bitta fayl — profil rasmi
app.post('/profil/rasm', upload.single('rasm'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'Fayl yuborilmadi' });
  }

  const fileUrl = `/uploads/${req.file.filename}`;
  res.json({
    message: 'Rasm yuklandi',
    url: fileUrl,
    hajm: req.file.size,
  });
});

// Xatolarni ushlash
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(400).json({ error: 'Fayl hajmi 5 MB dan oshmasligi kerak' });
    }
  }
  if (err.message) {
    return res.status(400).json({ error: err.message });
  }
  next(err);
});

app.listen(3000);

Bir nechta fayl yuklash

// Bir fielddan bir nechta fayl
app.post('/galereya', upload.array('rasmlar', 10), (req, res) => {
  //                               ↑ field nomi  ↑ maksimal son
  console.log(req.files); // ← fayllar massivi
  const urls = req.files.map(f => `/uploads/${f.filename}`);
  res.json({ rasmlar: urls });
});

// Turli fieldlardan fayllar
app.post('/mahsulot', upload.fields([
  { name: 'asosiy_rasm', maxCount: 1 },
  { name: 'qoshimcha_rasmlar', maxCount: 5 },
]), (req, res) => {
  const asosiy = req.files['asosiy_rasm']?.[0];
  const qoshimcha = req.files['qoshimcha_rasmlar'] || [];
  res.json({ asosiy: asosiy?.filename, qoshimcha: qoshimcha.map(f => f.filename) });
});

Fayl yuboradigan frontend

<!-- HTML forma -->
<form action="/profil/rasm" method="POST" enctype="multipart/form-data">
  <!--                                    ↑ bu majburiy -->
  <input type="file" name="rasm" accept="image/*">
  <button type="submit">Yuklash</button>
</form>

JavaScript bilan:

const formData = new FormData();
formData.append('rasm', fileInput.files[0]); // ← fayl qo'shiladi

const response = await fetch('/profil/rasm', {
  method: 'POST',
  body: formData,
  // Content-Type ni O'ZINGIZ QO'YMASLIK KERAK
  // Brauzer 'multipart/form-data; boundary=...' ni o'zi qo'yadi
});

Memory storage — diskga emas, xotiraga saqlash

Ba'zan faylni diskka saqlamay, to'g'ridan-to'g'RI qayta ishlash kerak (masalan Sharp bilan o'lchamni o'zgartirish):

const upload = multer({
  storage: multer.memoryStorage(), // ← disk o'rniga xotira
  limits: { fileSize: 5 * 1024 * 1024 }
});

app.post('/rasm-qayta-ishlash', upload.single('rasm'), async (req, res) => {
  // req.file.buffer — fayl ikkilik ma'lumoti sifatida xotirada
  const buffer = req.file.buffer;

  // Keyingi darsda: Sharp bilan resize qilish
  // const resized = await sharp(buffer).resize(300, 300).toBuffer();
});

Tez-tez uchraydigan xatolar

1. enctype="multipart/form-data" unutilgan

<!-- ❌ -->
<form action="/upload" method="POST">
  <input type="file" name="rasm">
</form>

Sababi: enctype bo'lmasa brauzer faylni yubormasdan faqat fayl nomini matn sifatida yuboradi. req.file undefined bo'ladi.

2. Multer xatolarini ushlash

// ❌ — Multer xatolari oddiy error handler ga tushmaydi
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});
// ✅ — multer.MulterError ni alohida tekshirish kerak
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    // LIMIT_FILE_SIZE, LIMIT_FILE_COUNT, LIMIT_UNEXPECTED_FILE
    return res.status(400).json({ error: err.message });
  }
  next(err);
});

Sababi: Multer o'zining MulterError classini ishlatadi — oddiy Error dan farqli.

3. uploads/ papkasini Git ga qo'shish

# .gitignore
uploads/

Sababi: foydalanuvchi fayllari Git da saqlanmasligi kerak. Repo hajmi kattalashadi, maxfiy fayllar ochiq bo'lib qolishi mumkin.

4. Fayl URL ni to'g'ri shakllanmaslik

// ❌ — path separator tizimga qarab farqlanadi
const url = 'uploads\\' + req.file.filename; // Windows da \ bo'ladi
// ✅ — path.join yoki slash bilan
const url = `/uploads/${req.file.filename}`;

Xulosa

  • Multer — multipart/form-data ni qayta ishlaydi, fayllarni req.file / req.files ga qo'yadi
  • diskStorage — fayl nomi va saqlash joyini boshqaradi
  • fileFilter — fayl turini mimetype orqali tekshiradi (kengaytmaga ishonmang)
  • limits.fileSize — hajm cheklovini baytda belgilaydi
  • multer.memoryStorage() — faylni diskka emas, xotiraga saqlaydi (keyingi qayta ishlash uchun)

Keyingi dars: 33-dars: Sharp — rasmlarni qayta ishlash, resize, compress