Back

Multer NPM: Datei-Upload in Node.js

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:

  1. Wir erstellen eine Multer-Instanz mit einem Zielordner für Uploads
  2. Wir verwenden die upload.single('document')-Middleware, um einen einzelnen Datei-Upload zu verarbeiten
  3. 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:

  1. Validieren Sie Dateitypen: Überprüfen Sie immer MIME-Typen und Erweiterungen
  2. Begrenzen Sie Dateigrößen: Verhindern Sie DoS-Angriffe durch große Datei-Uploads
  3. Scannen Sie auf Malware: Erwägen Sie die Integration von Virenscans für hochgeladene Dateien
  4. Verwenden Sie sichere Speicherung: Speichern Sie Dateien nicht in öffentlichen Verzeichnissen
  5. Generieren Sie zufällige Dateinamen: Verwenden Sie nicht direkt vom Benutzer bereitgestellte Dateinamen
  6. Implementieren Sie Authentifizierung: Stellen Sie sicher, dass nur autorisierte Benutzer Dateien hochladen können
  7. 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

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers