Handling File Uploads in Express with Multer
Also onpreet-jain.hashnode.dev/handling-file-uploads-in-express-with-multerIf you've ever tried handling file uploads in a Node.js app without middleware, you already know how quickly things get messy. Request bodies become unreadable, files end up nowhere, and suddenly you're writing more boilerplate than actual application logic. That's where Multer comes in.
Why File Uploads Need Middleware
Here's the thing about regular HTTP requests: they're great for text. JSON, form fields, query strings — all of it flows through the request body without a hitch. But files? They're binary blobs that don't fit neatly into the standard request parsing flow.
When a browser sends a file, it uses a special format called multipart/form-data. Instead of simple key-value pairs, the request body gets split into parts, each with its own headers containing filename and content type information. The raw body looks nothing like what you'd expect from a typical POST request.
Standard Express body parsers like express.json() don't know how to handle this format. They literally can't parse it. So you need something that understands multipart requests and can extract the file data before it reaches your route handlers.
That's exactly what file upload middleware does.
What Multer Is
Multer is Express middleware specifically designed for handling multipart/form-data. It extracts file data from incoming requests, stores it according to your configuration, and makes the file information available on the req.file or req.files object.
The name comes from "multipart" — get it? It's a portmanteau, which is charming in a nerdy way.
Multer doesn't handle multipart data from scratch. Under the hood, it builds on top of Busboy or Formidable, popular Node.js libraries for parsing multipart requests. But where those libraries are lower-level, Multer gives you a cleaner Express integration with sensible defaults.
Single File Upload
The most common scenario is uploading one file. Here's how that looks:
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('avatar'), (req, res) => {
console.log(req.file);
res.send('File uploaded!');
});A few things happening here. First, you create a multer instance with dest pointing to where files should go. That's your storage configuration — minimal for now.
The upload.single('avatar') middleware handles requests where the field name is avatar. When a request hits this route, Multer intercepts it, processes the file, and attaches req.file with details like original filename, size, and the path where it was saved.
The client side sends the file in a form with an input field named avatar:
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar" />
<button type="submit">Upload</button>
</form>The enctype="multipart/form-data" is crucial. Without it, the browser won't send the file properly.
Multiple File Uploads
Need to handle several files at once? Multer has you covered.
app.post('/photos', upload.array('photos', 5), (req, res) => {
console.log(req.files);
res.send(`${req.files.length} files uploaded`);
});upload.array('photos', 5) accepts up to 5 files from a field named photos. The files appear in req.files as an array, each object containing the same metadata you'd get from a single file upload.
You can also mix single and multiple uploads in the same route by combining middleware:
const upload = multer({ dest: 'uploads/' });
app.post('/profile',
upload.single('avatar'),
upload.array('gallery', 10),
(req, res) => {
// Both req.file and req.files are available
}
);Storage Configuration Basics
Basic disk storage gets you far, but sometimes you need more control. Multer's storage engine API lets you decide where files go and what they're named.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/profiles/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({ storage });The destination function lets you choose the folder dynamically — maybe based on user ID or file type. The filename function controls the actual filename saved to disk. In this example, we generate a timestamp-based unique name while preserving the original extension.
What's nice is that you can swap out the storage engine entirely. Want to store files in memory instead of on disk? Use memory storage for temporary processing. Want cloud storage? There's no built-in support, but you can implement a custom storage engine that uploads directly to S3 or similar services.
Serving Uploaded Files
Now that you can save files, you'll probably want to serve them back. Express makes this straightforward:
const path = require('path');
app.use('/uploads', express.static('uploads'));This serves everything in the uploads/ folder at the /uploads URL path. A file saved at uploads/photo.jpg becomes accessible at http://yourapp.com/uploads/photo.jpg.
For more controlled access — maybe you want authentication before serving — you can create a dynamic route:
app.get('/files/:filename', (req, res) => {
const filename = req.params.filename;
const filepath = path.join(__dirname, 'uploads', filename);
res.sendFile(filepath);
});This approach lets you add authorization checks, logging, or other logic before sending the file.
The Upload Lifecycle
Here's what the full upload flow looks like:
Client prepares
multipart/form-datarequest with file(s)Request hits Express router
Multer middleware intercepts the request before your route handler
Multer parses the multipart body, extracts file data
File gets saved to configured storage location
req.fileorreq.filesgets populated with metadataYour route handler runs, having access to file information
Response gets sent back to client
The key insight is that Multer runs before your handler. By the time your code executes, the file is already saved and ready to use.
Wrapping Up
Multer fills a real gap in Express development. Without it, you'd be wrestling with multipart parsing yourself — a rabbit hole nobody needs to go down. It handles the messy details while giving you just enough configuration to build real file upload features.
Start with basic disk storage, get your uploads working, then explore custom storage engines if you need them. The core API is small and predictable, which makes it easy to extend once you understand how it fits together.
One heads up: always validate uploaded files before using them. Check file types, enforce size limits, and sanitize filenames. File uploads are a common attack vector, and Multer won't do this for you automatically.