Hello readers π, welcome to the 12th blog in our Node.js series!
In the last post, we learned how Express simplifies route handling and request processing. Today, we're going to tackle a common realβworld requirement that every backend developer faces: handling file uploads. Whether it's a profile picture, a PDF invoice, or a batch of images, users need to send files to your server, and you need a clean way to receive and store them.
Express itself doesn't handle file uploads out of the box. That's where a specialized middleware called Multer comes in. We'll understand why file uploads need special handling, how Multer works, and then build practical examples for single and multiple file uploads. By the end, you'll be able to accept files, store them on disk, and serve them back to clients.
Let's jump right in.
Why file uploads need middleware
When a browser sends a file, it uses a special encoding called multipart/form-data. This encoding splits the request body into multiple parts, each representing a form field or a file. A regular JSON body parser like express.json() cannot parse multipart data. If you try, req.body will be empty, and the file data will be lost.
To handle multipart/form-data, we need a parser that knows how to extract files from the request stream and make them available to our code. Multer is precisely that parser.
What Multer is
Multer is a Node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is designed to work with Express and sits as a middleware in the request pipeline. When a request with a file arrives, Multer processes the incoming stream, saves the file(s) to a location you specify (disk or memory), and attaches a file (or files) object to the req object. The rest of your route handler can then access the file details.
Multer adds a file or files property to the request object. A single file upload sets req.file, while multiple files set req.files (an array or an object, depending on configuration). Each file object contains fields like originalname, mimetype, size, and path (if stored on disk).
The multipart/form-data concept simply
Imagine you want to mail a letter along with a small gift. You can't just stuff the gift into a regular envelope; you need a special package with compartments. Similarly, when a browser sends form data that includes a file, it can't just put everything into a single JSON object. It uses multipart/form-data, which creates boundaries (think of them as separators) that tell the server where each field or file begins and ends.
When you set enctype="multipart/form-data" on a form, the browser encodes the data using this format. Multer is the tool on the server side that understands these compartments and extracts the file from the correct part.
Setting up Multer
First, install Multer in your project:
npm install multer
Then, require it and configure a storage engine. We'll start with disk storage, which saves files directly to a folder on your server.
Minimal storage configuration
const multer = require('multer');
// Define where to store files and how to name them
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// Specify the upload folder
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
// Give the file a unique name, preserving the extension
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const extension = file.originalname.split('.').pop();
cb(null, file.fieldname + '-' + uniqueSuffix + '.' + extension);
}
});
const upload = multer({ storage: storage });
Here, multer.diskStorage lets you control the destination folder and filename. The callback cb is used to pass the final path or error. We generate a semiβunique filename to avoid collisions.
Handling a single file upload
Let's create an Express route that accepts a single file, for example, a profile picture. The client sends the file under a field name, say avatar.
const express = require('express');
const app = express();
const port = 3000;
// Ensure the "uploads" folder exists, or Multer will error
const fs = require('fs');
const path = require('path');
const uploadsDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadsDir)){
fs.mkdirSync(uploadsDir);
}
// Single file upload middleware
app.post('/profile', upload.single('avatar'), (req, res) => {
// req.file contains information about the uploaded file
if (!req.file) {
return res.status(400).json({ message: 'No file uploaded' });
}
console.log(req.file);
res.json({
message: 'File uploaded successfully',
file: {
originalName: req.file.originalname,
size: req.file.size,
path: req.file.path
}
});
});
app.listen(port, () => console.log(`Server running on port ${port}`));
upload.single('avatar') is the Multer middleware. It expects the file to be sent in the form field named avatar. After successful upload, req.file contains:
-
fieldnameβ the name of the form field -
originalnameβ the original name on the user's computer -
mimetypeβ e.g.,image/png -
sizeβ file size in bytes -
destinationβ the folder where the file was saved -
filenameβ the generated filename on disk -
pathβ the full path to the uploaded file
Handling multiple file uploads
Multer provides several ways to accept multiple files.
1. Multiple files with the same field name (array of files)
Use upload.array('photos', 5) to accept up to 5 files in the field photos.
app.post('/gallery', upload.array('photos', 5), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ message: 'No files uploaded' });
}
const fileDetails = req.files.map(f => ({
name: f.originalname,
size: f.size
}));
res.json({ message: 'Files uploaded', files: fileDetails });
});
req.files is an array of file objects, each similar to req.file.
2. Multiple fields with different names
Use upload.fields([...]) when you have several distinct file fields, like avatar and cover.
const cpUpload = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'cover', maxCount: 1 }
]);
app.post('/complete-profile', cpUpload, (req, res) => {
// req.files is an object with keys 'avatar' and 'cover', each an array of files
const avatar = req.files['avatar'] ? req.files['avatar'][0] : null;
const cover = req.files['cover'] ? req.files['cover'][0] : null;
res.json({ avatar: avatar?.originalname, cover: cover?.originalname });
});
Serving uploaded files
After uploading, you often need to serve the files back to the client (e.g., to display an image). Express has a builtβin middleware express.static for serving static files from a directory.
// Serve files from the 'uploads' folder at the '/files' URL path
app.use('/files', express.static(path.join(__dirname, 'uploads')));
Now, if a file was saved as uploads/avatar-123456789.png, the client can access it at http://localhost:3000/files/avatar-123456789.png. You can use the relative path to build the URL.
Visualizing the upload flow
Client β Server β Storage flow:
Client (browser)
β
βββ Sends POST /profile with multipart/form-data (file in field "avatar")
β
βΌ
Express Server
β
βββ Multer middleware (upload.single('avatar'))
β βββ Parses multipart data
β βββ Streams file to disk (or memory)
β βββ Attaches req.file
β
βΌ
Route Handler
β
βββ Accesses req.file details
βββ Saves metadata to database (if needed)
βββ Sends response (e.g., file URL)
Multer middleware execution:
Request
β
βΌ
Multer middleware (configured with storage engine)
β
βββ Read stream β Identify boundaries
β
βββ For each file field:
β βββ Extract filename, MIME type
β βββ Call storage._handleFile (disk or memory)
β β βββ Write to disk / buffer
β βββ Emit 'file' event or push to array
β
βββ Calls next() when all fields and files processed
β
βΌ
Next middleware / route handler (receives req.file or req.files)
That summarizes the lifecycle.
Conclusion
Multer makes handling file uploads in Express straightforward. By plugging it as a middleware, you can accept single or multiple files, control where they are saved, and then serve them back easily. Understanding multipart/form-data and Multer's role is key to building applications that accept userβgenerated content.
Let's quickly recap:
- File uploads use
multipart/form-data; regular body parsers can't handle them. - Multer is an Express middleware that parses multipart data and gives you
req.fileorreq.files. - Configure storage using
multer.diskStorageto define destination and filename. -
upload.single(),upload.array(), andupload.fields()cover most use cases. - Serve uploaded files with
express.staticfor easy access. - The upload lifecycle flows from client to Multer to disk, then to your route handler.
Now you can confidently add file upload capabilities to your Node.js APIs. In the next post, we might explore error handling in Multer or move on to database integration. See you then!
Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on LinkedIn and X, where I post more about web development.
Top comments (0)