Master file uploads in Node.js with Express using Multer middleware
Multer is a Node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It's built on top of busboy for maximum efficiency and works seamlessly with Express.js applications.
Allow users to upload and update their profile pictures in web applications.
Handle PDF, Word documents, spreadsheets for business applications.
Upload multiple images/videos for photo galleries or social media platforms.
Build cloud storage solutions like Dropbox or Google Drive.
Upload product images for online stores and marketplaces.
First, install Express and Multer packages:
npm init -y
npm install express multer
npm install --save-dev nodemon
Set up your project folder structure:
Create the main server file (index.js):
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Serve static files from uploads directory
app.use('/uploads', express.static('uploads'));
// Create uploads directory if it doesn't exist
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads');
}
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Set up disk storage configuration:
// Storage configuration
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// Specify the directory to store uploaded files
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
// Generate unique filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const fileExtension = path.extname(file.originalname);
const fileName = file.fieldname + '-' + uniqueSuffix + fileExtension;
cb(null, fileName);
}
});
// File filter function
const fileFilter = (req, file, cb) => {
// Accept only specific file types
const allowedTypes = /jpeg|jpg|png|gif|pdf|doc|docx/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Only images and documents are allowed!'));
}
};
// Initialize multer
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
},
fileFilter: fileFilter
});
Implement different types of file upload endpoints:
// Single file upload
app.post('/upload/single', upload.single('profilePic'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path,
url: `http://localhost:${PORT}/uploads/${req.file.filename}`
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Multiple files upload
app.post('/upload/multiple', upload.array('documents', 5), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' });
}
const fileDetails = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
url: `http://localhost:${PORT}/uploads/${file.filename}`
}));
res.json({
message: `${req.files.length} files uploaded successfully`,
files: fileDetails
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Mixed fields upload
app.post('/upload/mixed', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
]), (req, res) => {
try {
const result = {
message: 'Files uploaded successfully',
files: {}
};
if (req.files.avatar) {
result.files.avatar = req.files.avatar[0];
}
if (req.files.gallery) {
result.files.gallery = req.files.gallery;
}
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Create a simple HTML form for testing uploads:
// Serve HTML form
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>File Upload Demo</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.form-group { margin: 20px 0; }
input[type="file"] { margin: 10px 0; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
</style>
</head>
<body>
<h1>File Upload Demo</h1>
<div class="form-group">
<h3>Single File Upload</h3>
<form action="/upload/single" method="POST" enctype="multipart/form-data">
<input type="file" name="profilePic" required>
<button type="submit">Upload Single File</button>
</form>
</div>
<div class="form-group">
<h3>Multiple Files Upload</h3>
<form action="/upload/multiple" method="POST" enctype="multipart/form-data">
<input type="file" name="documents" multiple required>
<button type="submit">Upload Multiple Files</button>
</form>
</div>
</body>
</html>
`);
});
Implement comprehensive error handling:
// Error handling middleware
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large' });
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ error: 'Too many files' });
}
if (error.code === 'LIMIT_UNEXPECTED_FILE') {
return res.status(400).json({ error: 'Unexpected field' });
}
}
res.status(500).json({ error: error.message });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
| Storage Type | Description | Use Case |
|---|---|---|
diskStorage |
Saves files to disk | Permanent file storage |
memoryStorage |
Stores files in memory as Buffer | Temporary processing, cloud uploads |
const memoryStorage = multer.memoryStorage();
const uploadToMemory = multer({ storage: memoryStorage });
app.post('/upload/memory', uploadToMemory.single('file'), (req, res) => {
// File is available as req.file.buffer
console.log('File buffer size:', req.file.buffer.length);
// You can now upload to cloud services like AWS S3, Google Cloud, etc.
res.json({
message: 'File received in memory',
size: req.file.buffer.length,
mimetype: req.file.mimetype
});
});
const advancedUpload = multer({
storage: storage,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5, // Maximum 5 files
fields: 10, // Maximum 10 non-file fields
fieldNameSize: 100, // Maximum field name size
fieldSize: 1024 * 1024, // Maximum field value size (1MB)
headerPairs: 2000 // Maximum number of header key-value pairs
},
fileFilter: (req, file, cb) => {
// Custom validation logic
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed!'), false);
}
}
});
{
"message": "File uploaded successfully",
"file": {
"filename": "profilePic-1720256541234-123456789.jpg",
"originalname": "my-photo.jpg",
"mimetype": "image/jpeg",
"size": 245760,
"path": "uploads/profilePic-1720256541234-123456789.jpg",
"url": "http://localhost:3000/uploads/profilePic-1720256541234-123456789.jpg"
}
}
{
"message": "3 files uploaded successfully",
"files": [
{
"filename": "documents-1720256541234-123456789.pdf",
"originalname": "resume.pdf",
"mimetype": "application/pdf",
"size": 1024000,
"url": "http://localhost:3000/uploads/documents-1720256541234-123456789.pdf"
},
{
"filename": "documents-1720256541235-987654321.jpg",
"originalname": "photo.jpg",
"mimetype": "image/jpeg",
"size": 512000,
"url": "http://localhost:3000/uploads/documents-1720256541235-987654321.jpg"
}
]
}
| Method | Description | Example |
|---|---|---|
.single(fieldname) |
Accept single file | upload.single('avatar') |
.array(fieldname, maxCount) |
Accept array of files | upload.array('photos', 12) |
.fields(fields) |
Accept mixed fields | upload.fields([{name: 'avatar', maxCount: 1}]) |
.none() |
Accept only text fields | upload.none() |
.any() |
Accept any files | upload.any() |
node index.js
# Or with nodemon for development
npx nodemon index.js
Open your browser and navigate to http://localhost:3000
Use the HTML forms to test different upload scenarios
Check the uploads/ directory to see uploaded files
Access files via URL: http://localhost:3000/uploads/filename.ext