Multer — Single and Multi-File Uploads
File uploads are a common requirement: profile photos, product images, document attachments, CSV imports. In Express, the standard package for handling multipart/form-data (the encoding used for file uploads) is Multer. Multer parses the incoming request, extracts file buffers or writes them to disk, and populates req.file (single upload) or req.files (multiple uploads) so your route handler can process them.
Basic Setup and Storage Engines
Multer supports two storage engines: diskStorage (saves files to the local filesystem) and memoryStorage (stores files as Buffer objects in memory). Use memoryStorage when you intend to process files immediately and upload them to cloud storage (S3, Cloudinary) without touching disk. Use diskStorage for large files or when the file path needs to be stored in the database.
npm install multerconst multer = require('multer');
const path = require('path');
const crypto = require('crypto');
// Disk storage configuration
const diskStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploads'); // folder must exist
},
filename: (req, file, cb) => {
// Randomise filename to prevent path traversal / overwrites
const ext = path.extname(file.originalname);
const basename = crypto.randomBytes(16).toString('hex');
cb(null, basename + ext);
}
});
// Memory storage (for cloud upload workflow)
const memStorage = multer.memoryStorage();
const upload = multer({
storage: diskStorage,
limits: {
fileSize: 5 * 1024 * 1024 // 5 MB max
},
fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
if (allowed.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Only JPEG, PNG, and WebP images are allowed'), false);
}
}
});Single File Upload
upload.single('fieldName') returns middleware that processes one file from the multipart request field named fieldName. After the middleware runs, the file is available as req.file with properties: fieldname, originalname, mimetype, size, path (disk) or buffer (memory).
// Single file upload: profile photo
router.post(
'/users/avatar',
authenticate,
upload.single('avatar'),
async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
// req.file = {
// fieldname: 'avatar',
// originalname: 'photo.jpg',
// mimetype: 'image/jpeg',
// size: 204800,
// path: 'uploads/abc123def456.jpg', // (disk storage)
// filename: 'abc123def456.jpg'
// }
await User.updateOne(
{ _id: req.user.sub },
{ avatar: '/uploads/' + req.file.filename }
);
res.json({ avatar: '/uploads/' + req.file.filename });
}
);