Handling Uploads with Multer

express.json and express.urlencoded ignore multipart/form-data, the encoding a browser uses when a form contains <input type="file"> (Front-End Web Development covers the form side). Multer 2.4.0 (MIT) fills the gap, streaming each part as it arrives and putting text fields on req.body, files on req.file or req.files.

A disk-storage upload endpointJavaScript
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, UPLOAD_DIR),
  filename: (req, file, cb) => cb(null, `${crypto.randomUUID()}${ALLOWED.get(file.mimetype)}`)
});
const upload = multer({ storage, limits: { fileSize: 2 * 1024 * 1024, files: 3 } });
app.post('/cover', upload.single('cover'), (req, res) => {
  res.json({ original: req.file.originalname, mimetype: req.file.mimetype,
             size: req.file.size, stored: path.basename(req.file.path), body: req.body });
});

upload.single('cover') is middleware generated for one field: it takes one file under that name and rejects any other. upload.array('photos', 3) fills req.files, upload.fields([...]) accepts several named fields, and upload.none() parses a multipart body that must contain no files.

Output of 32
$ curl -F "cover=@cover.png;type=image/png" -F "title=Dune" http://127.0.0.1:4189/cover
{"original":"cover.png","mimetype":"image/png","size":70,
 "stored":"16a88f57-2226-4bb6-a120-fc62ed9ac7f1.png","body":{"title":"Dune"}}

The generated filename is the important line. originalname is attacker-controlled text: it can be ../../.ssh/authorized_keys or a duplicate of a file on disk. Multer never uses it for the path — omit filename and you get 16 random bytes as hex with no extension, in the system temp directory if you omit destination too. Generate your own from a UUID plus an extension derived from the validated type.

Of the two storage engines, diskStorage writes straight through, never holding the file in memory, and is the only sane choice for large files. memoryStorage gives you file.buffer, which suits small files you hash or forward to object storage at once — but every concurrent upload is resident RAM. Both sit on the same stream-based parser (Buffers and Streams); multer-s3 implements the same interface against S3-compatible storage.