Multer NPM: Datei-Upload in Node.js

Das Hochladen von Dateien ist eine häufige Anforderung in Webanwendungen, aber die korrekte Handhabung in Node.js kann herausfordernd sein. Multer ist ein Middleware für Express.js, das das Hochladen von Dateien unkompliziert und effizient gestaltet. Dieser Artikel erklärt, wie man Multer verwendet, um Datei-Uploads in Node.js-Anwendungen zu handhaben.
Wichtige Erkenntnisse
- Multer vereinfacht Datei-Uploads in Node.js durch die Verarbeitung von multipart/form-data
- Konfigurieren Sie Speicheroptionen nach Ihren Bedürfnissen (Festplatte, Arbeitsspeicher oder Cloud)
- Implementieren Sie angemessene Validierung und Filterung für mehr Sicherheit
- Setzen Sie geeignete Limits, um Missbrauch zu verhindern
- Behandeln Sie Fehler angemessen, um eine gute Benutzererfahrung zu gewährleisten
- Erwägen Sie Cloud-Speicher für Produktionsanwendungen
Was ist Multer?
Multer ist eine Node.js-Middleware, die speziell für die Verarbeitung von multipart/form-data
entwickelt wurde, dem Format, das beim Hochladen von Dateien über HTML-Formulare verwendet wird. Aufbauend auf busboy verarbeitet Multer Datei-Uploads effizient und macht sie in Ihren Express-Routen zugänglich.
Zu den Hauptfunktionen von Multer gehören:
- Verarbeitung von Einzel- und Mehrfach-Datei-Uploads
- Konfigurierbare Speicheroptionen
- Dateifilterungsmöglichkeiten
- Größenbeschränkungen und Validierung
- Nahtlose Integration mit Express.js
Verständnis von Multipart Form Data
Bevor wir tiefer in Multer einsteigen, ist es wichtig zu verstehen, warum die reguläre Formularverarbeitung für Datei-Uploads nicht funktioniert.
Wenn ein Formular Dateien enthält, kodiert der Browser es als multipart/form-data
anstelle des Standard-application/x-www-form-urlencoded
. Diese Kodierung ermöglicht die Übertragung von Binärdaten (Dateien) zusammen mit Textfeldern.
Die eingebauten Middleware-Funktionen von Express (express.json()
und express.urlencoded()
) können keine Multipart-Daten verarbeiten, weshalb wir Multer benötigen.
<!-- Dieses Formular sendet multipart/form-data -->
<form action=""/upload"" method=""POST"" enctype=""multipart/form-data"">
<input type=""file"" name=""document"">
<button type=""submit"">Upload</button>
</form>
Erste Schritte mit Multer
Installation
Installieren Sie zunächst Multer in Ihrem Node.js-Projekt:
npm install multer
Grundlegende Einrichtung
Hier ist ein einfaches Beispiel, wie man Multer in einer Express-Anwendung einrichtet:
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('document'), (req, res) => {
// req.file enthält Informationen über die hochgeladene Datei
console.log(req.file);
// req.body enthält alle Textfelder
console.log(req.body);
res.send('Datei erfolgreich hochgeladen');
});
app.listen(3000, () => {
console.log('Server läuft auf Port 3000');
});
In diesem Beispiel:
- Wir erstellen eine Multer-Instanz mit einem Zielordner für Uploads
- Wir verwenden die
upload.single('document')
-Middleware, um einen einzelnen Datei-Upload zu verarbeiten - Die Informationen zur hochgeladenen Datei sind in
req.file
verfügbar
Umgang mit verschiedenen Upload-Szenarien
Multer bietet mehrere Methoden, um verschiedene Datei-Upload-Szenarien zu handhaben:
Einzelner Datei-Upload
// Verarbeitung einer einzelnen Datei mit dem Feldnamen 'profile'
app.post('/profile', upload.single('profile'), (req, res) => {
// req.file enthält die hochgeladene Datei
res.json({
message: 'Datei erfolgreich hochgeladen',
file: req.file
});
});
Mehrere Dateien (gleiches Feld)
// Verarbeitung mehrerer Dateien (max. 5) mit dem Feldnamen 'photos'
app.post('/photos', upload.array('photos', 5), (req, res) => {
// req.files enthält ein Array von Dateien
res.json({
message: `${req.files.length} Dateien erfolgreich hochgeladen`,
files: req.files
});
});
Mehrere Dateien (verschiedene Felder)
const uploadFields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 3 }
]);
app.post('/profile', uploadFields, (req, res) => {
// req.files ist ein Objekt mit Feldnamen als Schlüssel
// req.files.avatar enthält die Avatar-Datei
// req.files.gallery enthält die Galerie-Dateien
res.json({
message: 'Dateien erfolgreich hochgeladen',
avatar: req.files.avatar,
gallery: req.files.gallery
});
});
Nur-Text-Multipart-Formulare
app.post('/form-data', upload.none(), (req, res) => {
// Verarbeitet nur Textfelder, lehnt alle Dateien ab
res.json(req.body);
});
Speicheroptionen
Multer bietet verschiedene Speicher-Engines, um zu kontrollieren, wo und wie Dateien gespeichert werden.
Festplattenspeicher
Für mehr Kontrolle über die Dateispeicherung verwenden Sie diskStorage
:
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'uploads/');
},
filename: function(req, file, cb) {
// Erstellen eines eindeutigen Dateinamens
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + getExtension(file.originalname));
}
});
function getExtension(filename) {
return filename.substring(filename.lastIndexOf('.'));
}
const upload = multer({ storage: storage });
Diese Konfiguration gibt Ihnen Kontrolle über:
- Wo Dateien gespeichert werden (destination)
- Wie sie benannt werden (filename)
Arbeitsspeicher
Wenn Sie Dateien im Speicher verarbeiten müssen, ohne sie auf die Festplatte zu speichern:
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.post('/upload', upload.single('file'), (req, res) => {
// req.file.buffer enthält die Dateidaten
// Verarbeiten Sie die Datei im Speicher (z.B. Upload in Cloud-Speicher)
// Beispiel: Datei-Buffer und -Typ abrufen
const fileBuffer = req.file.buffer;
const fileType = req.file.mimetype;
res.send('Datei verarbeitet');
});
Der Arbeitsspeicher ist nützlich, wenn:
- Sie Dateien an einen anderen Dienst weitergeben (wie S3 oder Cloudinary)
- Sie die Datei vor dem Speichern verarbeiten müssen
- Sie in serverlosen Umgebungen arbeiten
Dateifilterung
Sie können mit der Option fileFilter
kontrollieren, welche Dateien akzeptiert werden:
const fileFilter = (req, file, cb) => {
// Akzeptiert nur Bilddateien
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Nur Bilddateien sind erlaubt!'), false);
}
};
const upload = multer({
storage: multer.diskStorage({...}),
fileFilter: fileFilter
});
Größenbeschränkungen und Sicherheit
Das Setzen von Limits ist entscheidend für Sicherheit und Leistung:
const upload = multer({
storage: multer.diskStorage({...}),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB in Bytes
files: 5 // Maximal 5 Dateien pro Anfrage
}
});
Weitere Limit-Optionen umfassen:
fieldNameSize
: Maximale Feldnamengröße (Standard 100 Bytes)fieldSize
: Maximale Feldwertgröße (Standard 1MB)fields
: Maximale Anzahl von Nicht-Datei-Feldern (Standard Infinity)parts
: Maximale Anzahl von Teilen (Felder + Dateien) (Standard Infinity)
Fehlerbehandlung
Multer-Fehler sollten ordnungsgemäß behandelt werden, um aussagekräftiges Feedback zu geben:
app.post('/upload', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err instanceof multer.MulterError) {
// Ein Multer-Fehler ist aufgetreten
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'Datei zu groß' });
}
return res.status(400).json({ error: err.message });
} else if (err) {
// Ein unbekannter Fehler ist aufgetreten
return res.status(500).json({ error: 'Serverfehler' });
}
// Alles ist gut gelaufen
res.json({ message: 'Datei erfolgreich hochgeladen', file: req.file });
});
});
Vollständiges Beispiel: Aufbau eines Datei-Upload-Systems
Lassen Sie uns ein vollständiges Datei-Upload-System mit Frontend und Backend erstellen:
Backend (server.js)
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// Statische Dateien bereitstellen
app.use(express.static('public'));
// Speicher konfigurieren
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads';
// Verzeichnis erstellen, falls es nicht existiert
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
// Dateifilter konfigurieren
const fileFilter = (req, file, cb) => {
// Akzeptiert Bilder und PDFs
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Ungültiger Dateityp. Nur JPEG, PNG, GIF und PDF sind erlaubt.'), false);
}
};
// Multer konfigurieren
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5
}
});
// Route für einzelne Datei-Uploads
app.post('/upload/single', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ success: false, message: err.message });
} else if (err) {
return res.status(400).json({ success: false, message: err.message });
}
if (!req.file) {
return res.status(400).json({ success: false, message: 'Keine Datei bereitgestellt' });
}
res.json({
success: true,
message: 'Datei erfolgreich hochgeladen',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
}
});
});
});
// Route für mehrere Datei-Uploads
app.post('/upload/multiple', (req, res) => {
upload.array('files', 5)(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ success: false, message: err.message });
} else if (err) {
return res.status(400).json({ success: false, message: err.message });
}
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, message: 'Keine Dateien bereitgestellt' });
}
const fileDetails = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
path: file.path
}));
res.json({
success: true,
message: `${req.files.length} Dateien erfolgreich hochgeladen`,
files: fileDetails
});
});
});
// Liste der hochgeladenen Dateien abrufen
app.get('/files', (req, res) => {
const uploadDir = 'uploads';
fs.readdir(uploadDir, (err, files) => {
if (err) {
return res.status(500).json({ success: false, message: 'Fehler beim Lesen des Dateiverzeichnisses' });
}
res.json({
success: true,
files: files
});
});
});
app.listen(port, () => {
console.log(`Server läuft auf http://localhost:${port}`);
});
Frontend (public/index.html)
<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
<title>Datei-Upload mit Multer</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.upload-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.file-list {
margin-top: 20px;
}
.file-item {
padding: 10px;
margin: 5px 0;
background-color: #f5f5f5;
border-radius: 3px;
}
.error {
color: red;
margin-top: 10px;
}
.success {
color: green;
margin-top: 10px;
}
progress {
width: 100%;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Datei-Upload mit Multer</h1>
<div class=""upload-section"">
<h2>Einzelne Datei hochladen</h2>
<form id=""singleUploadForm"">
<input type=""file"" id=""singleFile"" name=""file"" required>
<button type=""submit"">Datei hochladen</button>
</form>
<div id=""singleUploadResult""></div>
<progress id=""singleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
</div>
<div class=""upload-section"">
<h2>Mehrere Dateien hochladen</h2>
<form id=""multipleUploadForm"">
<input type=""file"" id=""multipleFiles"" name=""files"" multiple required>
<button type=""submit"">Dateien hochladen</button>
</form>
<div id=""multipleUploadResult""></div>
<progress id=""multipleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
</div>
<div class=""upload-section"">
<h2>Hochgeladene Dateien</h2>
<button id=""refreshFiles"">Liste aktualisieren</button>
<div id=""fileList"" class=""file-list""></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Einzelner Datei-Upload
const singleUploadForm = document.getElementById('singleUploadForm');
const singleUploadResult = document.getElementById('singleUploadResult');
const singleUploadProgress = document.getElementById('singleUploadProgress');
singleUploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('singleFile');
if (!fileInput.files.length) {
singleUploadResult.innerHTML = '<div class=""error"">Bitte wählen Sie eine Datei aus</div>';
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
singleUploadProgress.style.display = 'block';
singleUploadProgress.value = 0;
singleUploadResult.innerHTML = '';
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
singleUploadProgress.value = percentComplete;
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
singleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
singleUploadForm.reset();
loadFiles();
} else {
let errorMessage = 'Upload fehlgeschlagen';
try {
const response = JSON.parse(xhr.responseText);
errorMessage = response.message || errorMessage;
} catch (e) {}
singleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
}
singleUploadProgress.style.display = 'none';
};
xhr.onerror = function() {
singleUploadResult.innerHTML = '<div class=""error"">Netzwerkfehler aufgetreten</div>';
singleUploadProgress.style.display = 'none';
};
xhr.open('POST', '/upload/single', true);
xhr.send(formData);
});
// Mehrfach-Datei-Upload
const multipleUploadForm = document.getElementById('multipleUploadForm');
const multipleUploadResult = document.getElementById('multipleUploadResult');
const multipleUploadProgress = document.getElementById('multipleUploadProgress');
multipleUploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('multipleFiles');
if (!fileInput.files.length) {
multipleUploadResult.innerHTML = '<div class=""error"">Bitte wählen Sie mindestens eine Datei aus</div>';
return;
}
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}
multipleUploadProgress.style.display = 'block';
multipleUploadProgress.value = 0;
multipleUploadResult.innerHTML = '';
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
multipleUploadProgress.value = percentComplete;
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
multipleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
multipleUploadForm.reset();
loadFiles();
} else {
let errorMessage = 'Upload fehlgeschlagen';
try {
const response = JSON.parse(xhr.responseText);
errorMessage = response.message || errorMessage;
} catch (e) {}
multipleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
}
multipleUploadProgress.style.display = 'none';
};
xhr.onerror = function() {
multipleUploadResult.innerHTML = '<div class=""error"">Netzwerkfehler aufgetreten</div>';
multipleUploadProgress.style.display = 'none';
};
xhr.open('POST', '/upload/multiple', true);
xhr.send(formData);
});
// Dateien laden
const fileList = document.getElementById('fileList');
const refreshFilesButton = document.getElementById('refreshFiles');
function loadFiles() {
fetch('/files')
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.files.length === 0) {
fileList.innerHTML = '<p>Noch keine Dateien hochgeladen</p>';
} else {
let html = '';
data.files.forEach(file => {
html += `<div class=""file-item"">${file}</div>`;
});
fileList.innerHTML = html;
}
} else {
fileList.innerHTML = `<div class=""error"">${data.message}</div>`;
}
})
.catch(error => {
fileList.innerHTML = '<div class=""error"">Fehler beim Laden der Dateien</div>';
});
}
refreshFilesButton.addEventListener('click', loadFiles);
// Initiales Laden
loadFiles();
});
</script>
</body>
</html>
Integration mit Cloud-Speicher
Für Produktionsanwendungen möchten Sie Dateien oft in Cloud-Speichern anstatt auf Ihrem Server speichern. Hier ist, wie man Multer mit AWS S3 integriert:
const AWS = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
// AWS konfigurieren
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
});
const s3 = new AWS.S3();
// Multer für die Verwendung mit S3 konfigurieren
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.S3_BUCKET_NAME,
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}`;
cb(null, uniqueName);
}
}),
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
}
});
app.post('/upload', upload.single('file'), (req, res) => {
res.json({
success: true,
message: 'Datei erfolgreich zu S3 hochgeladen',
fileLocation: req.file.location // S3 URL
});
});
Hinweis: Sie müssen das Paket multer-s3
installieren: npm install multer-s3 aws-sdk
.
Sicherheits-Best-Practices
Befolgen Sie diese Sicherheits-Best-Practices bei der Implementierung von Datei-Uploads:
- Validieren Sie Dateitypen: Überprüfen Sie immer MIME-Typen und Erweiterungen
- Begrenzen Sie Dateigrößen: Verhindern Sie DoS-Angriffe durch große Datei-Uploads
- Scannen Sie auf Malware: Erwägen Sie die Integration von Virenscans für hochgeladene Dateien
- Verwenden Sie sichere Speicherung: Speichern Sie Dateien nicht in öffentlichen Verzeichnissen
- Generieren Sie zufällige Dateinamen: Verwenden Sie nicht direkt vom Benutzer bereitgestellte Dateinamen
- Implementieren Sie Authentifizierung: Stellen Sie sicher, dass nur autorisierte Benutzer Dateien hochladen können
- Setzen Sie Upload-Limits: Beschränken Sie die Anzahl der Dateien pro Anfrage und pro Benutzer
Fazit
Multer bietet eine robuste Lösung für die Handhabung von Datei-Uploads in Node.js-Anwendungen. Indem Sie den in diesem Artikel beschriebenen Mustern und Praktiken folgen, können Sie sichere, effiziente und benutzerfreundliche Datei-Upload-Funktionen implementieren. Ob Sie einen einfachen Bild-Uploader oder ein komplexes Dokumentenmanagementsystem erstellen, Multers flexible API und Integrationsmöglichkeiten machen es zu einer ausgezeichneten Wahl für die Verarbeitung von multipart/form-data in Ihren Express-Anwendungen.
FAQs
Für große Dateien sollten Sie Folgendes in Betracht ziehen: Erhöhen Sie das Größenlimit in der Multer-Konfiguration, implementieren Sie Chunk-Uploads für sehr große Dateien, verwenden Sie Streaming, um Dateien zu verarbeiten, ohne sie vollständig in den Speicher zu laden, und implementieren Sie Fortschrittsverfolgung im Frontend.
Ja, Multer funktioniert gut mit TypeScript. Installieren Sie Typdefinitionen mit: npm install @types/multer