File uploads are a core feature in most modern web applications — whether it’s profile pictures, product images, documents, or CSV imports. Express.js doesn’t handle multipart/form-data out of the box, which is why we need Multer.
This is your one-stop, brain-friendly guide to mastering file uploads with Multer.
Why Do We Need Middleware for File Uploads?
HTML forms with enctype="multipart/form-data" send data differently from regular application/x-www-form-urlencoded forms.
- Text fields → easy
- Files → binary data + metadata
Node.js/Express req.body and req.query can’t parse this format natively. That’s where Multer comes in — it parses the multipart request, extracts files and fields, and attaches them to the request object (req.file / req.files).
Simple Analogy: Think of Multer as a helpful receptionist who opens the package (multipart request), sorts the documents (text fields) and gifts (files), and hands them to you in an organized way.
What is Multer?
Multer is a Node.js middleware for handling multipart/form-data. It’s built on top of busboy (very fast) and provides a clean API for:
- Saving files to disk
- Keeping files in memory
- Filtering files by type/size
- Renaming files
- Handling multiple files
Official npm: https://www.npmjs.com/package/multer
Installation
# npm
npm install multer
# pnpm
pnpm add multer
# yarn
yarn add multer
For TypeScript users:
# npm
npm install --save-dev @types/multer
# pnpm / yarn similarly
Project Setup
mkdir multer-express-upload
cd multer-express-upload
npm init -y
npm install express multer
Basic server.js:
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
const PORT = 3000;
// Serve static files (uploaded images)
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
Storage Configuration Basics
Multer needs a storage engine.
1. Disk Storage (Most Common)
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // folder must exist
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage });
2. Memory Storage (For Cloud Uploads)
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
Tip: Use memoryStorage when uploading directly to AWS S3, Cloudinary, etc.
Handling Single File Upload
HTML Form (index.html):
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar" />
<button type="submit">Upload</button>
</form>
Route:
app.post('/upload', upload.single('avatar'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded');
}
console.log(req.file);
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
path: `/uploads/${req.file.filename}`,
size: req.file.size,
mimetype: req.file.mimetype
}
});
});
req.file object contains:
-
fieldname,originalname,encoding,mimetype -
destination,filename,path,size
Handling Multiple Files
Multiple files with same field name
app.post('/upload-multiple', upload.array('photos', 5), (req, res) => { // max 5 files
res.json({
message: `${req.files.length} files uploaded`,
files: req.files
});
});
Multiple fields with different names
const uploadFields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 3 }
]);
app.post('/upload-fields', uploadFields, (req, res) => {
res.json({
avatar: req.files['avatar'],
documents: req.files['documents']
});
});
File Filtering & Validation (Very Important)
const fileFilter = (req, file, cb) => {
// Allowed extensions
const allowedTypes = /jpeg|jpg|png|gif|pdf/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb(new Error('Only images and PDFs are allowed!'));
}
};
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: fileFilter
});
Handle errors gracefully:
app.post('/upload', (req, res, next) => {
upload.single('avatar')(req, res, (err) => {
if (err) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).send('File too large (max 5MB)');
}
return res.status(400).send(err.message);
}
next();
});
}, (req, res) => {
// success handler
});
Multer Upload Lifecycle (Brain-Friendly Flow)
graph TD
A[Client sends multipart/form-data] --> B[Multer Middleware]
B --> C{Parse Request}
C --> D[Apply fileFilter]
D --> E{Check Limits}
E --> F[Save to storage]
F --> G[Attach to req.file / req.files]
G --> H[Your Route Handler]
Serving Uploaded Files
// Make sure this is before your routes
app.use('/uploads', express.static('uploads'));
For production, use Nginx or a CDN instead of serving static files through Node.js.
Complete Example with Folder Creation
const fs = require('fs');
// Create uploads folder if not exists
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
Best Practices & Pro Tips
- Always validate file types and sizes
-
Rename files — never trust
originalname(security + collision) - Use UUID for filenames in production
- Scan files for malware (ClamAV) in critical apps
- Clean up temporary files on error (memory storage)
- Use environment variables for upload paths
- Rate limit upload endpoints
- Compress images before saving (sharp library)
Recommended Additional Packages:
npm install sharp uuid express-rate-limit
# pnpm add sharp uuid express-rate-limit
# yarn add sharp uuid express-rate-limit
Common Pitfalls
- Forgetting
enctype="multipart/form-data" - Not creating the
uploadsdirectory - Using
upload.single()with multiple files - Serving large files directly from Express in production
- Not handling errors from Multer
Advanced: Upload to Cloud Storage
Once you’re comfortable with disk storage, move to:
- AWS S3 + Multer-S3
- Cloudinary
- Google Cloud Storage
I’ll write separate deep-dive blogs for these.
Conclusion
You now have everything you need to handle file uploads professionally in Express.js using Multer — from basics to production-ready patterns.
Master Multer → you can build profile systems, e-commerce product uploads, document management, and more.
Happy Coding! 🚀
References & Further Reading
- Official Multer Docs: https://github.com/expressjs/multer
- Express Static Files: https://expressjs.com/en/starter/static-files.html
- Handling Errors in Multer
- Image Optimization with Sharp
- Secure File Upload Best Practices (OWASP)
Other blogs in this Express Series:
- Express.js Routing Mastery
- Middleware Deep Dive in Express
- Building REST APIs with Express + MongoDB
- Authentication in Express (JWT + Passport)
Share this blog if it helped you! Drop your questions or your favorite upload tip in the comments.
Last updated: May 2026
Written for developers who want clean, secure, and scalable solutions.
Top comments (0)