Handling File Uploads

1. Installing Multer Middleware

PackagePurpose
multermultipart/form-data parser
multer-s3Stream to S3
multer-gridfs-storageMongoDB GridFS

2. Configuring Storage

Example: Memory vs disk storage

import multer from "multer";

// Memory — file in req.file.buffer
const memUpload = multer({ storage: multer.memoryStorage() });

// Disk — file persisted to disk
const diskUpload = multer({
  storage: multer.diskStorage({
    destination: "./uploads",
    filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
  })
});

3. Setting Upload Destination

StorageWhere
diskStorage({destination, filename})Local filesystem
memoryStorage()RAM (small files only)
multer-s3S3-compatible storage
CustomImplement _handleFile + _removeFile

4. Handling Single File Upload

Example: One file

app.post("/avatar",
  diskUpload.single("avatar"),  // form field name
  (req, res) => {
    res.json({
      filename: req.file.filename,
      size: req.file.size,
      mime: req.file.mimetype
    });
  }
);

5. Handling Multiple Files

Example: array() — multiple files, same field

app.post("/photos",
  diskUpload.array("photos", 10),  // max 10 files
  (req, res) => res.json({ count: req.files.length })
);

6. Handling Multiple Fields

Example: fields() — different field names

app.post("/listing",
  diskUpload.fields([
    { name: "cover", maxCount: 1 },
    { name: "gallery", maxCount: 8 }
  ]),
  (req, res) => {
    const cover = req.files.cover?.[0];
    const gallery = req.files.gallery || [];
    res.json({ cover: cover?.filename, gallery: gallery.map(f => f.filename) });
  }
);

7. Accessing Uploaded Files

PropertyDetail
req.fileSet by single()
req.filesArray (from array) or object (from fields)
req.bodyOther text fields from the form
file.fieldnameForm field name
file.originalnameClient filename
file.mimetypeMIME type from client (untrusted)
file.sizeBytes
file.bufferMemory storage only
file.pathDisk storage only

8. Filtering File Types

Example: fileFilter

const upload = multer({
  storage,
  fileFilter: (req, file, cb) => {
    const allowed = ["image/jpeg", "image/png", "image/webp"];
    if (!allowed.includes(file.mimetype)) {
      return cb(new Error("Only JPEG/PNG/WebP allowed"));
    }
    cb(null, true);
  }
});
Warning: file.mimetype comes from the client and can be spoofed. For security-critical filtering, sniff actual bytes with file-type package.

9. Setting File Size Limits

limits.*Purpose
fieldNameSizeMax field name length
fieldSizeMax non-file field value
fieldsMax non-file fields
fileSizeMax bytes per file
filesMax file count
partsMax parts (fields + files)
headerPairsMax multipart headers

Example: 5 MB image limit

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

10. Validating File Extensions

Example: Combine MIME + extension

import path from "node:path";

fileFilter: (req, file, cb) => {
  const ext = path.extname(file.originalname).toLowerCase();
  const allowedExt = [".jpg", ".jpeg", ".png", ".webp"];
  const allowedMime = ["image/jpeg", "image/png", "image/webp"];
  if (allowedExt.includes(ext) && allowedMime.includes(file.mimetype)) cb(null, true);
  else cb(new Error("Invalid file type"));
}

11. Renaming Uploaded Files

Example: UUID + extension

import { randomUUID } from "node:crypto";

multer.diskStorage({
  destination: "./uploads",
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname).toLowerCase();
    cb(null, `${randomUUID()}${ext}`);
  }
});

12. Handling Upload Errors

Example: MulterError handler

import multer from "multer";

app.post("/upload", upload.single("file"), handler);

app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    return res.status(400).json({ error: err.code, field: err.field });
  }
  next(err);
});
err.codeMeaning
LIMIT_FILE_SIZEFile too large
LIMIT_FILE_COUNTToo many files
LIMIT_FIELD_COUNTToo many text fields
LIMIT_UNEXPECTED_FILEUnexpected field name