Handling File Uploads
1. Installing Multer Middleware
| Package | Purpose |
multer | multipart/form-data parser |
multer-s3 | Stream to S3 |
multer-gridfs-storage | MongoDB 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
| Storage | Where |
diskStorage({destination, filename}) | Local filesystem |
memoryStorage() | RAM (small files only) |
multer-s3 | S3-compatible storage |
| Custom | Implement _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
| Property | Detail |
req.file | Set by single() |
req.files | Array (from array) or object (from fields) |
req.body | Other text fields from the form |
file.fieldname | Form field name |
file.originalname | Client filename |
file.mimetype | MIME type from client (untrusted) |
file.size | Bytes |
file.buffer | Memory storage only |
file.path | Disk 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 |
fieldNameSize | Max field name length |
fieldSize | Max non-file field value |
fields | Max non-file fields |
fileSize | Max bytes per file |
files | Max file count |
parts | Max parts (fields + files) |
headerPairs | Max 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.code | Meaning |
LIMIT_FILE_SIZE | File too large |
LIMIT_FILE_COUNT | Too many files |
LIMIT_FIELD_COUNT | Too many text fields |
LIMIT_UNEXPECTED_FILE | Unexpected field name |