Skip to main content

Command Palette

Search for a command to run...

Handling File Uploads in Express with Multer

We will understand how to upload files using Express and Multer

Published
•7 min read
Handling File Uploads in Express with Multer
S

Frontend Developer šŸ’» | Fueled by curiosity and Tea ā˜• | Always learning and exploring new technologies.

Why File Uploads Need Middleware

When you submit a plain HTML form name, email, that sort of thing the browser encodes everything as application/x-www-form-urlencoded. It's a simple key=value string, and Express's built-in body parser handles it without complaint.

But the moment a <input type="file"> enters the picture, the rules change. The browser switches to a completely different encoding: multipart/form-data. Instead of a flat string, it sends a binary stream divided into labeled sections called "parts" one for each field, one for each file. Each part has headers, a boundary marker, and its own raw content.

Think of multipart/form-data like a series of envelopes inside one package. Each envelope is a separate field — some contain plain text, others contain binary file data.

Express's default body parsers don't know how to open those envelopes. They'll parse JSON, they'll parse URL-encoded text, but multipart? They leave it alone. That's why you need dedicated middleware something that can read the binary stream, separate the parts, and hand them to your route handler in a usable shape.

Why not use body-parser?
The popular body-parser package explicitly does not support multipart bodies. Its README says so. This is by design multipart parsing is complex enough to warrant its own focused library.

What Multer Is

Multer is a Node.js middleware built specifically for handling multipart/form-data. It sits between the incoming request and your route handler, reads the binary stream, separates files from text fields, and attaches everything neatly to req.file or req.files.

Multer is built on top of busboy, a fast streaming multipart parser. It's the most widely used file upload middleware in the Express ecosystem, and for good reason it handles the messy parts so you don't have to.

Install it alongside Express:

npm install express multer

Multer's job is straightforward: intercept the request before your route handler runs, extract all the file and field data, save files to wherever you've configured, and populate req.body (for text fields) and req.file / req.files (for uploaded files).

The Upload Lifecycle

Before writing any code, it helps to see the full picture of what happens when a file travels from a browser to your server:

Three steps, every time: the browser sends the multipart stream, Multer intercepts and processes it, and your handler receives clean, ready-to-use file data.

Handling a Single File Upload

Let's start with the most common case a user uploading one file. The HTML side is just a standard form with enctype="multipart/form-data". Without that attribute, the file never makes it to the server.

Create a UI

<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="avatar" />
  <button type="submit">Upload</button>
</form>

On the server, Multer's .single(fieldName) method returns middleware that looks for one file in the named field:

const express = require('express');
const multer  = require('multer');

const app    = express();
const upload = multer({ dest: 'uploads/' });

// .single('avatar') → look for a field named "avatar"
app.post('/upload', upload.single('avatar'), (req, res) => {

  // req.file contains the uploaded file metadata
  // req.body contains any other text fields
  if (!req.file) {
    return res.status(400).json({ error: 'No file uploaded' });
  }

  res.json({
    message:      'Upload successful',
    filename:     req.file.filename,
    originalName: req.file.originalname,
    size:         req.file.size,
    mimetype:     req.file.mimetype,
  });
});

app.listen(3000);

After Multer runs, req.file is an object with everything you need to know about the uploaded file. The most useful properties: req.file

{
  fieldname:   'avatar',         // HTML field name
  originalname:'profile-pic.jpg',  // filename from user's machine
  encoding:    '7bit',
  mimetype:    'image/jpeg',       // detected content type
  destination: 'uploads/',         // folder where file was saved
  filename:    'a3f9c1b2d4e8...',  // generated unique name (no ext)
  path:        'uploads/a3f9c1...',// full path on disk
  size:        82034               // bytes
}

When using dest shorthand, Multer saves files without their original extension. The filename is a random hash. You'll need to rename it or use diskStorage if you want to preserve extensions — covered in the Storage section below.

Handling Multiple File Uploads

Two situations arise with multiple files. Either the user uploads several files through one input field, or you have multiple fields each accepting their own files. Multer has a method for each.

Multiple files from a single field

Use .array(fieldName, maxCount). The second argument is optional but highly recommended it caps how many files can arrive in one request:

app.post('/upload-many', upload.array('photos', 10), (req, res) => {

  // req.files is an array of file objects
  const filenames = req.files.map(f => f.filename);
  res.json({ uploaded: filenames });

});

Files from multiple fields

Use .fields() when each field has a distinct name. Pass an array of field descriptors:

const cpUpload = upload.fields([
  { name: 'avatar',   maxCount: 1  },
  { name: 'portfolio', maxCount: 5  },
]);

app.post('/profile', cpUpload, (req, res) => {

  // req.files is now an object, keyed by field name
  const avatar    = req.files['avatar'][0];    // one file
  const portfolio = req.files['portfolio'];    // array of files

  res.json({ avatarName: avatar.filename });
});

Storage Configuration Basics

The dest shorthand is fine for quick experiments, but production code usually needs more control. Multer's diskStorage engine gives you two configuration hooks: where to put the file, and what to name it.

const path   = require('path');
const multer = require('multer');

const storage = multer.diskStorage({

  // 1. Where should the file be saved?
  destination(req, file, cb) {
    cb(null, 'uploads/');  // first arg is error (null = no error)
  },

  // 2. What should the file be called?
  filename(req, file, cb) {
    const ext      = path.extname(file.originalname);
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, file.fieldname + '-' + uniqueSuffix + ext);
    // Result: "avatar-1718023456789-382910483.jpg"
  },

});

const upload = multer({ storage });

Both destination and filename follow the same node-style callback pattern: the first argument to cb is an error (pass null to proceed), and the second is your value. This pattern lets you run async logic like checking a database before deciding where a file should land.

File Type Filtering

You don't want users uploading executable files when you're expecting images. Multer lets you filter incoming files with a fileFilter function. If the filter rejects a file, the request continues without it — or you can throw an error:

function imageFilter(req, file, cb) {
  const allowed = ['image/jpeg', 'image/png', 'image/webp'];

  if (allowed.includes(file.mimetype)) {
    cb(null, true);   // accept the file
  } else {
    cb(new Error('Only JPEG, PNG, and WebP images are allowed'));
  }
}

const upload = multer({
  storage,
  fileFilter: imageFilter,
  limits: { fileSize: 5 * 1024 * 1024 }  // 5 MB cap
});

Serving Uploaded Files

Saving files is half the job. Once they're on disk, you need users to be able to retrieve them. Express's built-in express.static() middleware makes this trivially easy:

// Serve everything in the uploads/ folder under the /uploads URL path
app.use('/uploads', express.static('uploads'));

// A file saved at uploads/avatar-1718023456.jpg
// is now available at: http://localhost:3000/uploads/avatar-1718023456.jpg

That one line turns your upload folder into a file server. Any file sitting in uploads/ becomes publicly accessible via its filename in the URL.

Key Takeaways

  1. HTML forms must useenctype="multipart/form-data"— without it, no file data is sent.

  2. Multer interposes itself in the middleware chain, reading the stream before your handler runs.

  3. Use.single(),.array(), or.fields()depending on your form shape.

  4. diskStoragegives full control over folder and filename; use it in production.

  5. Always limit file size and filter by type to prevent abuse.

  6. express.static()