Skip to content

File Uploads in Express

Express has no built-in support for file uploads, multer is the standard middleware the Node ecosystem uses to parse multipart/form-data and expose uploaded files on req. This lesson covers setting it up correctly.

Setting Up Multer

multer is npm's most widely used middleware for handling multipart/form-data, the encoding required whenever a form includes file inputs. It parses incoming files, applies size/type limits you configure, and exposes them on req.file (single) or req.files (multiple).

You configure a storage engine, either diskStorage (writes files to disk with a filename you control) or the default memory storage (keeps the file as a Buffer in memory, useful when uploading straight to cloud storage).

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('avatar'), (req, res) => {
  res.json({ file: req.file });
});

upload.single('avatar') expects exactly one file field named avatar and attaches its metadata to req.file.

Multer Upload Variants

upload.single('fieldName')     // one file
upload.array('fieldName', max)  // multiple files, same field
upload.fields([{ name: 'a' }, { name: 'b' }]) // multiple named fields
upload.none()                   // no files, just text fields
  • req.file is populated by .single(); req.files is populated by .array()/.fields().
  • Set limits: { fileSize: ... } to reject oversized uploads before they consume disk/memory.
  • Use fileFilter to reject disallowed MIME types (e.g. only images).
  • Always validate uploaded content server-side, never trust the client-reported MIME type alone.

Multer Cheatsheet

The core multer configuration options you will reach for.

Option Example Purpose
Disk storage multer.diskStorage({ destination, filename }) Control where/how files are saved
Memory storage multer.memoryStorage() Keep file as a Buffer, no disk write
Size limit limits: { fileSize: 5 * 1024 * 1024 } Reject files over 5MB
Type filter fileFilter: (req, file, cb) => {...} Only allow certain MIME types
Single file upload.single('avatar') One file per request
Multiple files upload.array('photos', 10) Up to 10 files, one field

Disk Storage With Custom Filenames

Custom disk storage lets you control both the destination folder and the filename, typically avoiding collisions by adding a timestamp or unique ID.

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

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

const upload = multer({
  storage,
  limits: { fileSize: 5 * 1024 * 1024 },
  fileFilter: (req, file, cb) => {
    if (!file.mimetype.startsWith('image/')) {
      return cb(new Error('Only image uploads are allowed'));
    }
    cb(null, true);
  },
});

app.post('/upload', upload.single('avatar'), (req, res) => {
  res.json({ filename: req.file.filename });
});

Handling Multer Errors

Multer errors (like exceeding the size limit) are instances of multer.MulterError and should be caught in your error-handling middleware for a clean client response instead of a generic 500.

app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    return res.status(400).json({ error: err.message });
  }
  next(err);
});

Common Mistakes

  • Forgetting the form's enctype="multipart/form-data" attribute, so multer receives no file at all.
  • Not setting a file size limit, allowing arbitrarily large uploads to exhaust server resources.
  • Trusting the client-supplied MIME type without verifying actual file content for sensitive use cases.
  • Storing uploaded files directly inside the app's source directory instead of a dedicated uploads folder or cloud storage.

Key Takeaways

  • Multer is the standard middleware for parsing multipart/form-data file uploads in Express.
  • req.file holds a single upload; req.files holds multiple.
  • Always configure size limits and a file type filter for uploads.
  • Multer-specific errors should be caught and translated into clean 400 responses.

Pro Tip

For production apps, stream uploads directly to object storage (S3, Cloudinary, etc.) using memory storage, rather than writing to local disk, local files disappear on most modern hosting platforms after a redeploy.