[NODE JS] Express ile Dosya Yükleme (multer)
Dosya yükleme web uygulamalarının en temel ihtiyaçlarından biri. Profil fotoğrafı, belge, CV, ürün görseli gibi dosyaları kullanıcılardan almanız gerekir. Express'in built-in body parser'ları multipart/form-data formatını desteklemez çünkü bu format dosya aktarımı için özel olarak tasarlanmıştır. İşte burada multer devreye girer.
multer, Node.js için multipart/form-data işleme middleware'idir. npm install multer ile kurarsınız. Yüklenen dosyaları diske veya belleğe kaydedebilir. Dosya boyutu limiti, dosya tipi filtreleme, dosya adı özelleştirme gibi birçok yapılandırma seçeneği sunar.
Ben ilk dosya yükleme özelliğini yazarken hiç validasyon yapmamıştım. Birisi 500MB'lık bir dosya yüklemiş ve sunucu çökmüştü. O günden sonra dosya boyutu limiti, dosya tipi kontrolü ve depolama limiti gibi güvenlik önlemlerini ilk günden eklemeye başladım. Bu deneyim bana kullanıcıdan gelen her şeyin potansiyel olarak zararlı olabileceğini bir kez daha gösterdi.
multer'ın iki temel depolama seçeneği var: diskStorage ve memoryStorage. diskStorage dosyaları doğrudan diske yazar, büyük dosyalar için uygundur. memoryStorage dosyaları bellekte Buffer olarak tutar, dosyayı işleyip başka bir yere (S3 gibi) yüklemek istediğinizde kullanışlıdır.
diskStorage ile dosya adını ve hedef klasörü özelleştirebilirsiniz. Orijinal dosya adını kullanmak güvenlik riski oluşturabilir çünkü kötü niyetli dosya adları path traversal saldırısına yol açabilir. Ben her zaman UUID veya timestamp ile benzersiz dosya adı oluşturuyorum ve orijinal adı veritabanında saklıyorum.
Dosya tipi kontrolü çok önemli. Sadece belirli uzantıları kabul etmek yetmez çünkü uzantı değiştirilebilir. Dosyanın gerçek MIME type'ını kontrol etmeniz gerekir. multer'ın fileFilter fonksiyonu bu kontrolü yapmanızı sağlar. Ama MIME type bile güvenilir değildir, gerçek dosya tipini belirlemek için file-type gibi paketlerle dosyanın magic bytes'larını kontrol edebilirsiniz.
Yüklenen dosyaları public klasöre koymak büyük bir güvenlik riski. Birisi zararlı bir script yükleyip çalıştırabilir. Dosyaları ayrı bir klasöre koyun ve erişimi kontrol edin. Dosyaları servis ederken doğrudan statik dosya olarak sunmak yerine bir route üzerinden kontrollü bir şekilde sunmak daha güvenli.
Büyük dosya yüklemelerinde progress tracking kullanıcı deneyimi açısından önemli. Frontend'de XMLHttpRequest veya fetch API ile progress event dinleyebilirsiniz. Backend'de ise multer otomatik olarak dosyayı chunk'lar halinde alır ve yazar. Bağlantı koparsa kısmi dosyalar kalabilir, bunları temizleyen bir mekanizma kurmanız gerekir.
Cloud storage entegrasyonu modern uygulamalarda standart. AWS S3, Google Cloud Storage veya Azure Blob Storage kullanarak dosyaları bulutta saklayabilirsiniz. multer-s3 paketi ile doğrudan S3'e yükleme yapabilirsiniz. Bu ölçeklenebilirlik, yedekleme ve CDN entegrasyonu açısından çok avantajlı.
JavaScript:
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const app = express();
// npm install multer
// Yukleme klasorunu olustur
const UPLOAD_DIR = path.join(__dirname, 'uploads');
const AVATAR_DIR = path.join(UPLOAD_DIR, 'avatarlar');
const BELGE_DIR = path.join(UPLOAD_DIR, 'belgeler');
[UPLOAD_DIR, AVATAR_DIR, BELGE_DIR].forEach(dir => {{
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {{ recursive: true }});
}});
// ===== DISK STORAGE YAPILANDIRMASI =====
const diskStorage = multer.diskStorage({{
destination: (req, file, cb) => {{
// Dosya tipine gore farkli klasor
if (file.fieldname === 'avatar') {{
cb(null, AVATAR_DIR);
}} else {{
cb(null, BELGE_DIR);
}}
}},
filename: (req, file, cb) => {{
// Benzersiz dosya adi olustur
const uniqueId = crypto.randomBytes(16).toString('hex');
const uzanti = path.extname(file.originalname).toLowerCase();
cb(null, `${{uniqueId}}${{uzanti}}`);
}},
}});
// ===== DOSYA TIPI FILTRESI =====
function dosyaFiltresi(izinliTipler) {{
return (req, file, cb) => {{
// MIME type kontrolu
if (izinliTipler.includes(file.mimetype)) {{
cb(null, true);
}} else {{
cb(new Error(
`Gecersiz dosya tipi: ${{file.mimetype}}. Izin verilen: ${{izinliTipler.join(', ')}}`
), false);
}}
}};
}}
// ===== MULTER INSTANCELARI =====
// Avatar yukleme (tek dosya, max 2MB, sadece resim)
const avatarUpload = multer({{
storage: diskStorage,
limits: {{
fileSize: 2 * 1024 * 1024, // 2MB
files: 1,
}},
fileFilter: dosyaFiltresi([
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
]),
}});
// Belge yukleme (birden fazla dosya, max 10MB)
const belgeUpload = multer({{
storage: diskStorage,
limits: {{
fileSize: 10 * 1024 * 1024, // 10MB
files: 5,
}},
fileFilter: dosyaFiltresi([
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'image/jpeg',
'image/png',
]),
}});
// ===== ROUTE'LAR =====
// Ana sayfa - yukleme formu
app.get('/', (req, res) => {{
res.send(`
<h1>Dosya Yukleme</h1>
<h2>Avatar Yukle (Tek Dosya)</h2>
<form action="/upload/avatar" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*" required>
<button type="submit">Yukle</button>
</form>
<h2>Belge Yukle (Birden Fazla)</h2>
<form action="/upload/belgeler" method="POST" enctype="multipart/form-data">
<input type="file" name="belgeler" multiple accept=".pdf,.doc,.docx,.jpg,.png" required>
<button type="submit">Yukle</button>
</form>
<h2>Karisik Yukleme</h2>
<form action="/upload/karisik" method="POST" enctype="multipart/form-data">
<label>Profil Fotografi:</label>
<input type="file" name="avatar" accept="image/*"><br><br>
<label>Belgeler:</label>
<input type="file" name="belgeler" multiple><br><br>
<label>Adiniz:</label>
<input type="text" name="ad"><br><br>
<button type="submit">Yukle</button>
</form>
<p><a href="/dosyalar">Yuklenen Dosyalari Gor</a></p>
`);
}});
// Tek dosya yukleme
app.post('/upload/avatar', avatarUpload.single('avatar'), (req, res) => {{
if (!req.file) {{
return res.status(400).json({{ hata: 'Dosya secilmedi' }});
}}
res.json({{
mesaj: 'Avatar yuklendi',
dosya: {{
orijinalAd: req.file.originalname,
kaydedilenAd: req.file.filename,
boyut: `${{(req.file.size / 1024).toFixed(1)}} KB`,
mimeTip: req.file.mimetype,
yol: `/uploads/avatarlar/${{req.file.filename}}`,
}},
}});
}});
// Birden fazla dosya yukleme
app.post('/upload/belgeler', belgeUpload.array('belgeler', 5), (req, res) => {{
if (!req.files || req.files.length === 0) {{
return res.status(400).json({{ hata: 'Dosya secilmedi' }});
}}
const dosyalar = req.files.map(f => ({{
orijinalAd: f.originalname,
kaydedilenAd: f.filename,
boyut: `${{(f.size / 1024).toFixed(1)}} KB`,
mimeTip: f.mimetype,
}}));
res.json({{
mesaj: `${{dosyalar.length}} dosya yuklendi`,
dosyalar,
}});
}});
// Karisik alanlardan yukleme
const karisikUpload = multer({{ storage: diskStorage, limits: {{ fileSize: 5 * 1024 * 1024 }} }});
app.post('/upload/karisik',
karisikUpload.fields([
{{ name: 'avatar', maxCount: 1 }},
{{ name: 'belgeler', maxCount: 3 }},
]),
(req, res) => {{
res.json({{
mesaj: 'Karisik yukleme basarili',
ad: req.body.ad,
avatar: req.files['avatar'] ? req.files['avatar'][0].filename : null,
belgeler: req.files['belgeler'] ? req.files['belgeler'].map(f => f.filename) : [],
}});
}}
);
// Yuklenen dosyalari listele
app.get('/dosyalar', (req, res) => {{
const avatarlar = fs.existsSync(AVATAR_DIR) ? fs.readdirSync(AVATAR_DIR) : [];
const belgeler = fs.existsSync(BELGE_DIR) ? fs.readdirSync(BELGE_DIR) : [];
res.json({{ avatarlar, belgeler }});
}});
// Dosya silme
app.delete('/dosyalar/:dosyaAdi', (req, res) => {{
const dosyaAdi = req.params.dosyaAdi;
// Path traversal onlemi
if (dosyaAdi.includes('..') || dosyaAdi.includes('/') || dosyaAdi.includes('\\')) {{
return res.status(400).json({{ hata: 'Gecersiz dosya adi' }});
}}
const yollar = [
path.join(AVATAR_DIR, dosyaAdi),
path.join(BELGE_DIR, dosyaAdi),
];
for (const yol of yollar) {{
if (fs.existsSync(yol)) {{
fs.unlinkSync(yol);
return res.json({{ mesaj: 'Dosya silindi' }});
}}
}}
res.status(404).json({{ hata: 'Dosya bulunamadi' }});
}});
// Statik dosya sunma (yuklenen dosyalar icin)
app.use('/uploads', express.static(UPLOAD_DIR));
// ===== MULTER HATA YAKALAMA =====
app.use((err, req, res, next) => {{
if (err instanceof multer.MulterError) {{
const hataMesajlari = {{
LIMIT_FILE_SIZE: 'Dosya boyutu cok buyuk',
LIMIT_FILE_COUNT: 'Cok fazla dosya yuklemeye calisiyorsunuz',
LIMIT_UNEXPECTED_FILE: 'Beklenmeyen dosya alani',
}};
return res.status(400).json({{
hata: hataMesajlari[err.code] || err.message,
kod: err.code,
}});
}}
if (err.message && err.message.includes('Gecersiz dosya tipi')) {{
return res.status(400).json({{ hata: err.message }});
}}
next(err);
}});
app.listen(3000, () => {{
console.log('Dosya yukleme sunucusu http://localhost:3000');
}});
Dosya yükleme güvenlik açısından dikkatli olunması gereken bir konu. Her zaman dosya boyutu limiti koyun, dosya tipini doğrulayın, orijinal dosya adını kullanmayın ve yüklenen dosyaları web root dışında saklayın. Bu basit kurallara uyarsanız çoğu güvenlik riskini bertaraf edersiniz.
Emeğiniz ve katkınız için teşekkür ederiz!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)
Benzer konular
- Cevaplar
- 9
- Görüntüleme
- 229
- Kilitli
- Cevaplar
- 7
- Görüntüleme
- 2K
- Kilitli
- Cevaplar
- 1
- Görüntüleme
- 431
- Cevaplar
- 0
- Görüntüleme
- 52
- Cevaplar
- 4
- Görüntüleme
- 297