emirhanHCL 1
emirhanHCL
kralhakan2009 1
kralhakan2009
Vahsi Uzman 1
Vahsi Uzman
farkmt2official 1
farkmt2official
mannaxxx 1
mannaxxx
Bvural41 1
Bvural41
Agora Metin2 1
Agora Metin2
berkmenoo 1
berkmenoo
Hikaye Ekle

[NODE JS] Express ile Çoklu Dosya Yükleme

nodejs.png


[NODE JS] Express ile Çoklu Dosya Yükleme​


Tek dosya yüklemek kolaydır ama gerçek dünya uygulamalarında genellikle birden fazla dosya yüklemeniz gerekir. Bir ürüne birden fazla fotoğraf eklemek, bir form ile birlikte CV ve kapak mektubu göndermek veya bir galeri oluşturmak için toplu dosya yükleme gerekir. multer bu senaryoların hepsini destekler.

multer'da çoklu dosya yükleme için üç ana yöntem var: array(), fields() ve any(). array() aynı input alanından birden fazla dosya alır. fields() farklı input alanlarından farklı dosyalar alır. any() ise herhangi bir alandan gelen tüm dosyaları kabul eder ama güvenlik açısından pek önerilmez.

Ben bir emlak sitesi projesinde çoklu fotoğraf yükleme özelliği geliştiriyordum. Kullanıcılar ilan için 20-30 fotoğraf yüklüyordu ve sunucu bunları sırayla işliyordu. İşlem çok yavaştı. Sonra dosyaları paralel olarak resize edip sıkıştırmayı öğrendim ve performans dramatik şekilde arttı. sharp kütüphanesi bu iş için harika çalışıyor.

Çoklu dosya yüklemede dosya sayısı limiti koymak çok önemli. maxCount parametresi ile bir alandaki maksimum dosya sayısını belirleyebilirsiniz. Ayrıca toplam dosya boyutunu da sınırlamalısınız. Bir kullanıcının 100 tane büyük dosya yükleyerek sunucunuzu çökertmesini istemezsiniz.

Her dosya için ayrı ayrı validasyon yapılmalıdır. Bir dosya geçersiz olduğunda diğer dosyaları da reddetmek mi yoksa sadece geçersiz olanı atlamak mı istediğinize karar vermeniz gerekir. Genellikle hepsini reddetmek daha güvenlidir çünkü kısmi yükleme tutarsızlığa neden olabilir.

Dosya sırasını korumak da önemli olabilir. Kullanıcı fotoğrafları belirli bir sırada yüklediğinde bu sırayı veritabanında saklamanız gerekir. req.files dizisi dosyaları yüklenme sırasıyla içerir. Her dosyaya bir sıra numarası atayıp veritabanında bu bilgiyi tutmanızı öneririm.

Yükleme sırasında hata olursa zaten yüklenmiş dosyaları temizlemek gerekir. Bu transaction benzeri bir davranıştır: ya hepsi başarılı olur ya da hiçbiri kaydedilmez. Dosyaları önce geçici bir klasöre yükleyip, tüm validasyonlar geçtikten sonra kalıcı klasöre taşımak bu sorunu çözer.

Drag and drop ile dosya yükleme modern web uygulamalarında standart hale geldi. Frontend'de dropzone.js veya kendi drag-drop implementasyonunuzu kullanabilirsiniz. Backend tarafında fark yok, aynı multipart/form-data formatı kullanılır. Ama frontend deneyimini iyileştirmek için progress bar, önizleme ve sürükle-bırak desteği eklemenizi tavsiye ederim.

Resim dosyaları için thumbnail oluşturmak yaygın bir ihtiyaç. sharp kütüphanesi ile yüklenen resimleri farklı boyutlarda resize edebilir, formatını değiştirebilir ve sıkıştırabilirsiniz. Orijinal dosyayı koruyup farklı boyutlarda kopyalar oluşturmak hem bant genişliği hem de performans açısından çok önemli.

JavaScript:
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Klasorleri olustur
const UPLOAD_DIR = path.join(__dirname, 'uploads');
const TEMP_DIR = path.join(UPLOAD_DIR, 'temp');
const GALERI_DIR = path.join(UPLOAD_DIR, 'galeri');
const BELGELER_DIR = path.join(UPLOAD_DIR, 'belgeler');

[UPLOAD_DIR, TEMP_DIR, GALERI_DIR, BELGELER_DIR].forEach(dir => {{
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, {{ recursive: true }});
}});

// Storage yapilandirmasi
const storage = multer.diskStorage({{
  destination: (req, file, cb) => {{
    cb(null, TEMP_DIR); // Once gecici klasore kaydet
  }},
  filename: (req, file, cb) => {{
    const id = crypto.randomBytes(12).toString('hex');
    const ext = path.extname(file.originalname).toLowerCase();
    cb(null, `${{Date.now()}}-${{id}}${{ext}}`);
  }},
}});

// Resim filtresi
const resimFiltresi = (req, file, cb) => {{
  const izinliTipler = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
  if (izinliTipler.includes(file.mimetype)) {{
    cb(null, true);
  }} else {{
    cb(new Error(`Sadece resim dosyalari kabul edilir. Gonderilen: ${{file.mimetype}}`), false);
  }}
}};

// Multer instancelari
const galeriUpload = multer({{
  storage,
  limits: {{ fileSize: 5 * 1024 * 1024, files: 10 }}, // Max 5MB, max 10 dosya
  fileFilter: resimFiltresi,
}});

const formUpload = multer({{
  storage,
  limits: {{ fileSize: 10 * 1024 * 1024, files: 5 }},
}});

// Gecici dosyalari kalici klasore tasi
function dosyaTasi(dosyalar, hedefKlasor) {{
  return dosyalar.map(dosya => {{
    const hedefYol = path.join(hedefKlasor, dosya.filename);
    fs.renameSync(dosya.path, hedefYol);
    return {{
      orijinalAd: dosya.originalname,
      kaydedilenAd: dosya.filename,
      boyut: dosya.size,
      mimeTip: dosya.mimetype,
      yol: hedefYol,
    }};
  }});
}}

// Gecici dosyalari temizle (hata durumunda)
function geciciTemizle(dosyalar) {{
  if (!dosyalar) return;
  const liste = Array.isArray(dosyalar) ? dosyalar : Object.values(dosyalar).flat();
  liste.forEach(dosya => {{
    try {{
      if (fs.existsSync(dosya.path)) fs.unlinkSync(dosya.path);
    }} catch (e) {{
      console.error('Gecici dosya silinemedi:', e.message);
    }}
  }});
}}

// ===== ROUTE'LAR =====

app.get('/', (req, res) => {{
  res.send(`
    <h1>Coklu Dosya Yukleme</h1>

    <h2>Galeri (Ayni Alandan Coklu)</h2>
    <form action="/upload/galeri" method="POST" enctype="multipart/form-data">
      <input type="text" name="galeriAdi" placeholder="Galeri Adi" required><br><br>
      <input type="file" name="resimler" multiple accept="image/*" required><br>
      <small>Max 10 resim, her biri max 5MB</small><br><br>
      <button type="submit">Yukle</button>
    </form>

    <h2>Is Basvurusu (Farkli Alanlardan)</h2>
    <form action="/upload/basvuru" method="POST" enctype="multipart/form-data">
      <input type="text" name="adSoyad" placeholder="Ad Soyad" required><br><br>
      <label>Profil Fotografi:</label>
      <input type="file" name="fotograf" accept="image/*"><br><br>
      <label>CV (PDF):</label>
      <input type="file" name="cv" accept=".pdf" required><br><br>
      <label>Ek Belgeler:</label>
      <input type="file" name="ekBelgeler" multiple accept=".pdf,.doc,.docx"><br><br>
      <button type="submit">Basvur</button>
    </form>
  `);
}});

// Galeri yukleme - array() ile ayni alandan coklu dosya
app.post('/upload/galeri', galeriUpload.array('resimler', 10), (req, res) => {{
  try {{
    if (!req.files || req.files.length === 0) {{
      return res.status(400).json({{ hata: 'En az bir resim secmelisiniz' }});
    }}

    const galeriAdi = req.body.galeriAdi || 'Isimsiz Galeri';

    // Dosyalari kalici klasore tasi
    const kaydedilenler = dosyaTasi(req.files, GALERI_DIR);

    res.status(201).json({{
      mesaj: `${{kaydedilenler.length}} resim yuklendi`,
      galeri: galeriAdi,
      dosyalar: kaydedilenler.map((d, i) => ({{
        sira: i + 1,
        ad: d.orijinalAd,
        boyut: `${{(d.boyut / 1024).toFixed(1)}} KB`,
        url: `/uploads/galeri/${{d.kaydedilenAd}}`,
      }})),
    }});
  }} catch (err) {{
    geciciTemizle(req.files);
    throw err;
  }}
}});

// Is basvurusu - fields() ile farkli alanlardan dosya
app.post('/upload/basvuru',
  formUpload.fields([
    {{ name: 'fotograf', maxCount: 1 }},
    {{ name: 'cv', maxCount: 1 }},
    {{ name: 'ekBelgeler', maxCount: 3 }},
  ]),
  (req, res) => {{
    try {{
      const sonuc = {{
        adSoyad: req.body.adSoyad,
        fotograf: null,
        cv: null,
        ekBelgeler: [],
      }};

      // Her alani ayri isle
      if (req.files['fotograf']) {{
        const f = dosyaTasi(req.files['fotograf'], GALERI_DIR);
        sonuc.fotograf = f[0].kaydedilenAd;
      }}

      if (req.files['cv']) {{
        const f = dosyaTasi(req.files['cv'], BELGELER_DIR);
        sonuc.cv = f[0].kaydedilenAd;
      }}

      if (req.files['ekBelgeler']) {{
        const f = dosyaTasi(req.files['ekBelgeler'], BELGELER_DIR);
        sonuc.ekBelgeler = f.map(d => d.kaydedilenAd);
      }}

      res.status(201).json({{
        mesaj: 'Basvuru alindi',
        basvuru: sonuc,
      }});
    }} catch (err) {{
      geciciTemizle(req.files);
      throw err;
    }}
  }}
);

// Yuklenen dosyalari sun
app.use('/uploads', express.static(UPLOAD_DIR));

// Hata yakalama
app.use((err, req, res, next) => {{
  if (err instanceof multer.MulterError) {{
    const mesajlar = {{
      LIMIT_FILE_SIZE: 'Dosya boyutu limiti asildi',
      LIMIT_FILE_COUNT: 'Dosya sayisi limiti asildi',
      LIMIT_UNEXPECTED_FILE: 'Beklenmeyen dosya alani',
    }};
    return res.status(400).json({{ hata: mesajlar[err.code] || err.message }});
  }}
  if (err.message) {{
    return res.status(400).json({{ hata: err.message }});
  }}
  res.status(500).json({{ hata: 'Sunucu hatasi' }});
}});

app.listen(3000, () => {{
  console.log('Coklu dosya yukleme sunucusu http://localhost:3000');
}});

Çoklu dosya yükleme tamamlandığında kullanıcıya yüklenen dosyaların listesini gösterin. Başarısız olan dosyalar varsa nedenini belirtin. Yükleme tamamlandıktan sonra dosyaları yeniden sıralama veya silme imkanı tanıyın. Kullanıcı deneyimi dosya yükleme sürecinin ne kadar iyi olduğunu belirler.
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst