Handling File Uploads in Express with Multer

Search for a command to run...

No comments yet. Be the first to comment.
You open WhatsApp, type a message, and hit send. A single grey tick appears. You're on a train with no signal, but the message went through anyway. Or did it? That single tick is the beginning of a su

You record a Reel, add a song, trim it, and then close the app before posting. When you reopen Instagram, the draft is right there waiting. Nothing was lost. That experience feels simple. The system b

If you have built a React Native app, you have felt the pain of navigation setup. It is easy to get wrong and hard to keep clean as apps grow. This article explains what routing means, why it matters,

Modern apps are not just a pile of screens. They are a living system of features, data flows, offline behavior, and performance constraints. If you have only shipped small apps, the jump to something

Your application has user accounts. Some routes should only be accessible to logged-in users. How do you protect routes and verify that a request actually comes from who they claim to be? JWT (JSON We

Ashish's Blog
57 posts
Users need to upload files: profile pictures, documents, resumes, attachments. Handling this in Express requires special handling. A library called Multer makes it straightforward.
Regular form data is text. But files are binary. When uploading files, the request format changes to multipart/form-data, which handles both text fields and binary files.
Express doesn't automatically parse this format. That's where Multer comes in.
Multer is middleware for Express that handles file uploads. It parses multipart form data, extracts files, and saves them to disk or memory.
Install it:
npm install multer
Set up Multer to handle a single file:
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded');
}
res.send(`File ${req.file.filename} uploaded`);
});
app.listen(3000);
multer({ dest: 'uploads/' }) saves files to the uploads/ folder
upload.single('file') handles one file from a form field named "file"
req.file contains file information
A user posts a file to /upload, and Multer saves it to uploads/.
After upload, req.file contains:
{
fieldname: 'file',
originalname: 'photo.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/',
filename: 'file-1234567890',
path: 'uploads/file-1234567890',
size: 154321
}
Use this information to track what was uploaded:
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded');
}
res.json({
message: 'File uploaded successfully',
filename: req.file.filename,
originalName: req.file.originalname,
size: req.file.size,
path: req.file.path
});
});
Handle multiple files with upload.array():
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload-multiple', upload.array('files'), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).send('No files uploaded');
}
const fileInfo = req.files.map(file => ({
filename: file.filename,
originalName: file.originalname,
size: file.size
}));
res.json({ files: fileInfo });
});
app.listen(3000);
req.files is an array of uploaded files.
You can limit the number of files:
upload.array('files', 5) // Max 5 files
By default, Multer generates random filenames. Configure storage to control this:
import express from 'express';
import multer from 'multer';
import path from 'path';
const app = express();
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
// Keep original extension
const ext = path.extname(file.originalname);
const name = path.basename(file.originalname, ext);
const uniqueName = name + '-' + Date.now() + ext;
cb(null, uniqueName);
}
});
const upload = multer({ storage });
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ filename: req.file.filename });
});
app.listen(3000);
Now files are named: photo-1234567890.jpg instead of random names.
Validate file types to prevent unwanted uploads:
import express from 'express';
import multer from 'multer';
const app = express();
const storage = multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
// Allow only images
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'));
}
};
const upload = multer({ storage, fileFilter });
app.post('/upload-image', upload.single('image'), (req, res) => {
res.json({ message: 'Image uploaded', filename: req.file.filename });
});
app.listen(3000);
The fileFilter function checks the MIME type and rejects non-image files.
Prevent huge files from being uploaded:
const upload = multer({
dest: 'uploads/',
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
}
});
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ message: 'File uploaded' });
});
Files larger than 5MB are rejected.
Upload different types of files in one request:
const app = express();
const upload = multer({ dest: 'uploads/' });
const fileUpload = upload.fields([
{ name: 'profilePic', maxCount: 1 },
{ name: 'coverPhoto', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]);
app.post('/upload-profile', fileUpload, (req, res) => {
const profilePic = req.files.profilePic?.[0];
const coverPhoto = req.files.coverPhoto?.[0];
const documents = req.files.documents;
res.json({
profilePic: profilePic?.filename,
coverPhoto: coverPhoto?.filename,
documentCount: documents?.length || 0
});
});
app.listen(3000);
req.files contains objects for each field name.
Here's a complete user profile upload system:
import express from 'express';
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.use(express.json());
// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads/profiles';
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const name = 'profile-' + Date.now() + ext;
cb(null, name);
}
});
// Filter to allow only images
const fileFilter = (req, file, cb) => {
const allowedMimes = ['image/jpeg', 'image/png'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Only JPEG and PNG images allowed'));
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 2 * 1024 * 1024 } // 2MB
});
// Serve uploaded files
app.use('/images', express.static('uploads/profiles'));
// Upload endpoint
app.post('/upload-profile', upload.single('profilePic'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const imageUrl = `/images/${req.file.filename}`;
res.json({
message: 'Profile picture uploaded',
filename: req.file.filename,
url: imageUrl,
size: req.file.size
});
});
// Error handler for Multer
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'FILE_TOO_LARGE') {
return res.status(400).json({ error: 'File too large' });
}
return res.status(400).json({ error: err.message });
}
if (err) {
return res.status(400).json({ error: err.message });
}
next();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Store files in memory instead of disk (useful for processing before saving):
const storage = multer.memoryStorage();
const upload = multer({ storage });
app.post('/process-file', upload.single('file'), (req, res) => {
// File is in req.file.buffer (as a Buffer)
const content = req.file.buffer.toString();
res.json({ message: 'File processed' });
});
Mistake 1: Not handling errors
// Wrong - crashes on file too large
app.post('/upload', upload.single('file'), (req, res) => {
res.send('Done');
});
// Right - add error handler
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
res.status(400).json({ error: err.message });
} else {
res.status(500).json({ error: 'Server error' });
}
});
Mistake 2: Trusting original filename
// Wrong - security risk
const filename = file.originalname;
// Right - generate safe filename
const filename = Date.now() + '-' + file.originalname;
Mistake 3: Not validating file type
Always validate using MIME type or file extension, preferably both.
Multer is middleware for handling file uploads in Express
Use upload.single() for one file, upload.array() for multiple
Configure storage to control where and how files are saved
Use fileFilter to validate file types
Set size limits to prevent huge uploads
Always generate unique, safe filenames
Use error handlers to gracefully handle upload errors
Serve uploaded files as static files with express.static
File uploads are common in web applications. Master Multer, and you can confidently handle user-uploaded content.