DEV Community

Cover image for Handling File Uploads in Express with Multer
SATYA SOOTAR
SATYA SOOTAR

Posted on

Handling File Uploads in Express with Multer

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
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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}`));
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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')));
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.file or req.files.
  • Configure storage using multer.diskStorage to define destination and filename.
  • upload.single(), upload.array(), and upload.fields() cover most use cases.
  • Serve uploaded files with express.static for 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)