Handling File Uploads in Express with Multer
If you are building a full-stack application, eventually, you are going to need users to upload something. Whether it is a profile picture, a PDF resume, or a gallery of product images, handling file uploads can feel like dark magic the first time you try it.
You might try sending a file to your Express server just like you send regular JSON data, only to find that your server has absolutely no idea what to do with it.
Let's break down exactly why that happens, how the multipart/form-data concept works, and how to use a tool called Multer to make file uploads incredibly easy.
1. Why Express Can't Handle Files Out of the Box
Imagine you are sending information to a friend. Sending regular text data (like a username or password in JSON) is like sending a postcard. It is lightweight, the text is right there on the back, and the mail carrier (Express) can read it instantly.
Sending a file (like an image or a video) is entirely different. It is like shipping a heavy, sealed package. You can't just stick a postcard stamp on a package and expect it to arrive. You need a special shipping method.
On the web, this special shipping method is called multipart/form-data. When an HTML form sends a file, it chops the data into "multiple parts" so it can be streamed securely over the internet.
By default, Express only knows how to read postcards (JSON or URL-encoded text). If you hand it a heavy package (multipart/form-data), it simply ignores it. We need a dedicated "package handler" to open the box, take the file out, and put it somewhere safe.
That is where middleware comes in.
2. Enter Multer: The Package Handler
Multer is a middleware specifically designed for Express.js. Its only job is to handle multipart/form-data.
When a user submits a file, Multer intercepts the request before it reaches your main route logic. It reads the incoming file stream, saves the file to a folder on your computer, and then attaches a neat little summary of the file to the req object so you can use it.
The Upload Lifecycle
User uploads
profile.jpgvia a form.The request hits your Express server.
Multer intercepts it. Multer saves the image to a folder (e.g.,
/uploads).Multer creates an object with the file's details (filename, size, path) and attaches it to
req.file.Your route handler runs, and you can now save the file path to your database.
3. Basic Setup & Storage Configuration
Before we upload anything, we need to install Multer:
npm install multer
Next, we need to tell Multer where to put the files and what to name them. We do this using multer.diskStorage.
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// Configure where and how the files are saved
const storage = multer.diskStorage({
destination: (req, file, cb) => {
// Tell Multer to save files in the 'uploads' folder
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
// Give the file a unique name (timestamp + original name)
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
// Initialize Multer with our storage engine
const upload = multer({ storage: storage });
(Note: Make sure you actually create a folder named uploads in your project directory, or Node will throw an error!)
4. Handling a Single File Upload
Now that Multer is configured, we can inject it into our routes as middleware. If we are expecting a single file (like an avatar), we use upload.single('fieldName').
The string inside .single() MUST exactly match the name attribute of the input field in your frontend form.
// The middleware upload.single('avatar') runs first
app.post('/upload-profile', upload.single('avatar'), (req, res) => {
// If Multer failed, req.file will be undefined
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
// Multer attached the file info to req.file
console.log(req.file);
/*
req.file looks like this:
{
fieldname: 'avatar',
originalname: 'my-photo.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/',
filename: '16987654321-my-photo.jpg',
path: 'uploads\\16987654321-my-photo.jpg',
size: 102450
}
*/
res.send(`File uploaded successfully! Saved as ${req.file.filename}`);
});
5. Handling Multiple File Uploads
What if you are building a product page and need to upload 5 images at once? Instead of .single(), we use upload.array('fieldName', maxCount).
// Accept an array of files from the 'gallery' input, up to a max of 5 files
app.post('/upload-gallery', upload.array('gallery', 5), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).send('No files uploaded.');
}
// Notice it is req.files (plural) now! It is an array of objects.
console.log(req.files);
res.send(`${req.files.length} files uploaded successfully!`);
});
6. Serving the Uploaded Files
Uploading files is only half the battle. If a user uploads a profile picture, they expect to see it on their screen.
By default, Express protects all the folders in your project. If someone tries to visit http://localhost:3000/uploads/my-photo.jpg, Express will block them and return a 404 error.
You have to explicitly tell Express to make your uploads folder public. We do this using the built-in express.static middleware.
// Serve the 'uploads' directory publicly
app.use('/uploads', express.static('uploads'));
Now, if you have a file named 123-avatar.jpg inside your uploads folder, your frontend can display it normally using an image tag: <img src="http://localhost:3000/uploads/123-avatar.jpg" />
Summary
Handling files doesn't have to be complicated if you understand the flow:
Files travel as
multipart/form-data, which Express ignores by default.Multer is the middleware that intercepts, reads, and saves these files to your server's disk.
Use
upload.single()for one file, which attaches data toreq.file.Use
upload.array()for multiple files, which attaches an array toreq.files.Expose your upload folder using
express.staticso the frontend can actually display the images.
Stay Tune for next Blog! Happy Coding!
