Multer NPM: File Upload in Node.js

File uploads are a common requirement in web applications, but handling them properly in Node.js can be challenging. Multer is a middleware for Express.js that makes file uploads straightforward and efficient. This article explores how to use Multer to handle file uploads in your Node.js applications.
Key Takeaways
- Multer simplifies file uploads in Node.js by handling multipart/form-data
- Configure storage options based on your needs (disk, memory, or cloud)
- Implement proper validation and filtering to ensure security
- Set appropriate limits to prevent abuse
- Handle errors gracefully to provide a good user experience
- Consider cloud storage for production applications
What is Multer?
Multer is a Node.js middleware specifically designed for handling multipart/form-data
, which is the format used when uploading files through HTML forms. Built on top of busboy, Multer efficiently processes file uploads and makes them accessible in your Express routes.
Key features of Multer include:
- Handling single and multiple file uploads
- Configurable storage options
- File filtering capabilities
- Size limits and validation
- Seamless integration with Express.js
Understanding Multipart Form Data
Before diving into Multer, it’s important to understand why regular form processing doesn’t work for file uploads.
When a form contains files, the browser encodes it as multipart/form-data
instead of the default application/x-www-form-urlencoded
. This encoding allows binary data (files) to be transmitted alongside text fields.
Express’s built-in middleware (express.json()
and express.urlencoded()
) can’t process multipart data, which is why we need Multer.
<!-- This form will send multipart/form-data -->
<form action=""/upload"" method=""POST"" enctype=""multipart/form-data"">
<input type=""file"" name=""document"">
<button type=""submit"">Upload</button>
</form>
Getting Started with Multer
Installation
First, install Multer in your Node.js project:
npm install multer
Basic Setup
Here’s a simple example of how to set up Multer in an Express application:
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('document'), (req, res) => {
// req.file contains information about the uploaded file
console.log(req.file);
// req.body contains any text fields
console.log(req.body);
res.send('File uploaded successfully');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
In this example:
- We create a Multer instance with a destination folder for uploads
- We use
upload.single('document')
middleware to handle a single file upload - The uploaded file information is available in
req.file
Handling Different Upload Scenarios
Multer provides several methods to handle different file upload scenarios:
Single File Upload
// Handle a single file with the field name 'profile'
app.post('/profile', upload.single('profile'), (req, res) => {
// req.file contains the uploaded file
res.json({
message: 'File uploaded successfully',
file: req.file
});
});
Multiple Files (Same Field)
// Handle multiple files (max 5) with the field name 'photos'
app.post('/photos', upload.array('photos', 5), (req, res) => {
// req.files contains an array of files
res.json({
message: `${req.files.length} files uploaded successfully`,
files: req.files
});
});
Multiple Files (Different Fields)
const uploadFields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 3 }
]);
app.post('/profile', uploadFields, (req, res) => {
// req.files is an object with field names as keys
// req.files.avatar contains avatar file
// req.files.gallery contains gallery files
res.json({
message: 'Files uploaded successfully',
avatar: req.files.avatar,
gallery: req.files.gallery
});
});
Text-Only Multipart Forms
app.post('/form-data', upload.none(), (req, res) => {
// Only process text fields, reject any files
res.json(req.body);
});
Storage Options
Multer provides different storage engines to control where and how files are stored.
Disk Storage
For more control over file storage, use diskStorage
:
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'uploads/');
},
filename: function(req, file, cb) {
// Create a unique filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + getExtension(file.originalname));
}
});
function getExtension(filename) {
return filename.substring(filename.lastIndexOf('.'));
}
const upload = multer({ storage: storage });
This configuration gives you control over:
- Where files are stored (destination)
- What they’re named (filename)
Memory Storage
If you need to process files in memory without saving to disk:
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.post('/upload', upload.single('file'), (req, res) => {
// req.file.buffer contains the file data
// Process the file in memory (e.g., upload to cloud storage)
// Example: Get file buffer and type
const fileBuffer = req.file.buffer;
const fileType = req.file.mimetype;
res.send('File processed');
});
Memory storage is useful when:
- You’re passing files to another service (like S3 or Cloudinary)
- You need to process the file before saving
- You’re working in serverless environments
File Filtering
You can control which files are accepted using the fileFilter
option:
const fileFilter = (req, file, cb) => {
// Accept only image files
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed!'), false);
}
};
const upload = multer({
storage: multer.diskStorage({...}),
fileFilter: fileFilter
});
Size Limits and Security
Setting limits is crucial for security and performance:
const upload = multer({
storage: multer.diskStorage({...}),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB in bytes
files: 5 // Maximum 5 files per request
}
});
Other limit options include:
fieldNameSize
: Max field name size (default 100 bytes)fieldSize
: Max field value size (default 1MB)fields
: Max number of non-file fields (default Infinity)parts
: Max number of parts (fields + files) (default Infinity)
Error Handling
Multer errors should be handled properly to provide meaningful feedback:
app.post('/upload', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err instanceof multer.MulterError) {
// A Multer error occurred
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large' });
}
return res.status(400).json({ error: err.message });
} else if (err) {
// An unknown error occurred
return res.status(500).json({ error: 'Server error' });
}
// Everything went fine
res.json({ message: 'File uploaded successfully', file: req.file });
});
});
Complete Example: Building a File Upload System
Let’s build a complete file upload system with frontend and backend:
Backend (server.js)
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// Serve static files
app.use(express.static('public'));
// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads';
// Create directory if it doesn't exist
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
// Configure file filter
const fileFilter = (req, file, cb) => {
// Accept images and PDFs
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and PDF are allowed.'), false);
}
};
// Configure multer
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5
}
});
// Single file upload route
app.post('/upload/single', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ success: false, message: err.message });
} else if (err) {
return res.status(400).json({ success: false, message: err.message });
}
if (!req.file) {
return res.status(400).json({ success: false, message: 'No file provided' });
}
res.json({
success: true,
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
}
});
});
});
// Multiple file upload route
app.post('/upload/multiple', (req, res) => {
upload.array('files', 5)(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ success: false, message: err.message });
} else if (err) {
return res.status(400).json({ success: false, message: err.message });
}
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, message: 'No files provided' });
}
const fileDetails = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
path: file.path
}));
res.json({
success: true,
message: `${req.files.length} files uploaded successfully`,
files: fileDetails
});
});
});
// Get list of uploaded files
app.get('/files', (req, res) => {
const uploadDir = 'uploads';
fs.readdir(uploadDir, (err, files) => {
if (err) {
return res.status(500).json({ success: false, message: 'Error reading files directory' });
}
res.json({
success: true,
files: files
});
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Frontend (public/index.html)
<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
<title>File Upload with Multer</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.upload-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.file-list {
margin-top: 20px;
}
.file-item {
padding: 10px;
margin: 5px 0;
background-color: #f5f5f5;
border-radius: 3px;
}
.error {
color: red;
margin-top: 10px;
}
.success {
color: green;
margin-top: 10px;
}
progress {
width: 100%;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>File Upload with Multer</h1>
<div class=""upload-section"">
<h2>Single File Upload</h2>
<form id=""singleUploadForm"">
<input type=""file"" id=""singleFile"" name=""file"" required>
<button type=""submit"">Upload File</button>
</form>
<div id=""singleUploadResult""></div>
<progress id=""singleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
</div>
<div class=""upload-section"">
<h2>Multiple File Upload</h2>
<form id=""multipleUploadForm"">
<input type=""file"" id=""multipleFiles"" name=""files"" multiple required>
<button type=""submit"">Upload Files</button>
</form>
<div id=""multipleUploadResult""></div>
<progress id=""multipleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
</div>
<div class=""upload-section"">
<h2>Uploaded Files</h2>
<button id=""refreshFiles"">Refresh List</button>
<div id=""fileList"" class=""file-list""></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Single file upload
const singleUploadForm = document.getElementById('singleUploadForm');
const singleUploadResult = document.getElementById('singleUploadResult');
const singleUploadProgress = document.getElementById('singleUploadProgress');
singleUploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('singleFile');
if (!fileInput.files.length) {
singleUploadResult.innerHTML = '<div class=""error"">Please select a file</div>';
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
singleUploadProgress.style.display = 'block';
singleUploadProgress.value = 0;
singleUploadResult.innerHTML = '';
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
singleUploadProgress.value = percentComplete;
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
singleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
singleUploadForm.reset();
loadFiles();
} else {
let errorMessage = 'Upload failed';
try {
const response = JSON.parse(xhr.responseText);
errorMessage = response.message || errorMessage;
} catch (e) {}
singleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
}
singleUploadProgress.style.display = 'none';
};
xhr.onerror = function() {
singleUploadResult.innerHTML = '<div class=""error"">Network error occurred</div>';
singleUploadProgress.style.display = 'none';
};
xhr.open('POST', '/upload/single', true);
xhr.send(formData);
});
// Multiple file upload
const multipleUploadForm = document.getElementById('multipleUploadForm');
const multipleUploadResult = document.getElementById('multipleUploadResult');
const multipleUploadProgress = document.getElementById('multipleUploadProgress');
multipleUploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('multipleFiles');
if (!fileInput.files.length) {
multipleUploadResult.innerHTML = '<div class=""error"">Please select at least one file</div>';
return;
}
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}
multipleUploadProgress.style.display = 'block';
multipleUploadProgress.value = 0;
multipleUploadResult.innerHTML = '';
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
multipleUploadProgress.value = percentComplete;
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
multipleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
multipleUploadForm.reset();
loadFiles();
} else {
let errorMessage = 'Upload failed';
try {
const response = JSON.parse(xhr.responseText);
errorMessage = response.message || errorMessage;
} catch (e) {}
multipleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
}
multipleUploadProgress.style.display = 'none';
};
xhr.onerror = function() {
multipleUploadResult.innerHTML = '<div class=""error"">Network error occurred</div>';
multipleUploadProgress.style.display = 'none';
};
xhr.open('POST', '/upload/multiple', true);
xhr.send(formData);
});
// Load files
const fileList = document.getElementById('fileList');
const refreshFilesButton = document.getElementById('refreshFiles');
function loadFiles() {
fetch('/files')
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.files.length === 0) {
fileList.innerHTML = '<p>No files uploaded yet</p>';
} else {
let html = '';
data.files.forEach(file => {
html += `<div class=""file-item"">${file}</div>`;
});
fileList.innerHTML = html;
}
} else {
fileList.innerHTML = `<div class=""error"">${data.message}</div>`;
}
})
.catch(error => {
fileList.innerHTML = '<div class=""error"">Error loading files</div>';
});
}
refreshFilesButton.addEventListener('click', loadFiles);
// Initial load
loadFiles();
});
</script>
</body>
</html>
Integration with Cloud Storage
For production applications, you often want to store files in cloud storage rather than on your server. Here’s how to integrate Multer with AWS S3:
const AWS = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
// Configure AWS
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
});
const s3 = new AWS.S3();
// Configure multer to use S3
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.S3_BUCKET_NAME,
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}`;
cb(null, uniqueName);
}
}),
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
}
});
app.post('/upload', upload.single('file'), (req, res) => {
res.json({
success: true,
message: 'File uploaded to S3 successfully',
fileLocation: req.file.location // S3 URL
});
});
Note: You’ll need to install the multer-s3
package: npm install multer-s3 aws-sdk
.
Security Best Practices
When implementing file uploads, follow these security best practices:
- Validate file types: Always check MIME types and extensions
- Limit file sizes: Prevent DoS attacks from large file uploads
- Scan for malware: Consider integrating virus scanning for uploaded files
- Use secure storage: Don’t store files in public directories
- Generate random filenames: Don’t use user-provided filenames directly
- Implement authentication: Ensure only authorized users can upload files
- Set upload limits: Restrict the number of files per request and per user
Conclusion
Multer provides a robust solution for handling file uploads in Node.js applications. By following the patterns and practices outlined in this article, you can implement secure, efficient, and user-friendly file upload functionality. Whether you’re building a simple image uploader or a complex document management system, Multer’s flexible API and integration capabilities make it an excellent choice for handling multipart/form-data in your Express applications.
FAQs
For large files, consider: increasing the size limit in Multer configuration, implementing chunked uploads for very large files, using streaming to process files without loading them entirely into memory, and implementing progress tracking on the frontend.
Yes, Multer works well with TypeScript. Install type definitions with: npm install @types/multer