Skip to main content

Command Palette

Search for a command to run...

Handling File Uploads in Express with Multer

Updated
10 min read
Handling File Uploads in Express with Multer

1. Why file uploads need middleware


2. What Multer is

Multer is a Node.js middleware for handling multipart/form-data, which is the encoding type used when browsers upload files via HTML forms.
It sits on top of busboy and makes it easy to access uploaded files and form fields in your Express applications.

Key features:

  • Accepts single or multiple files

  • Stores files on disk or in memory

  • Filters files by type/size

  • Renames uploaded files automatically

  • Provides error handling for upload issues

2.Suggestions (Best Practices & Tips)

Always validate file types – use fileFilter to reject unexpected extensions/MIME types (e.g., only images, PDFs).

Limit file size – set limits: { fileSize: 2 * 1024 * 1024 } to avoid memory/disk abuse.

Use disk storage in development, but consider cloud storage (S3, GCS) in production – after mastering the basics.

Clean up temporary files if using memory storage – large files can exhaust RAM.

Handle errors gracefully – Multer errors (e.g., LIMIT_FILE_SIZE) should be caught and sent back to the client.

Don’t store uploaded files directly in static public folders without renaming and sanitising names (security risk).

Keep Multer middleware specific to routes that need uploads – not globally applied.

3. Explain multipart/form-data Concept (Simply)

When a browser sends a regular form (application/x-www-form-urlencoded), data is packed like name=John&age=30.
But files are binary data – they cannot be sent that way.

multipart/form-data breaks the request into multiple parts (hence “multipart”), each part having:

  • A Content-Disposition header with the field name (and filename for file parts)

  • Its own content type (e.g., image/jpeg)

  • The actual binary data

Example of a multipart request body (simplified):

---------------------------boundary
Content-Disposition: form-data; name="username"

john_doe
---------------------------boundary
Content-Disposition: form-data; name="avatar"; filename="me.jpg"
Content-Type: image/jpeg

(binary image data here...)
---------------------------boundary--

Multer parses this complex structure so you can simply access req.file or req.files and req.body for text fields.

. Upload Lifecycle (Clearly)

  1. Browser sends POST request with enctype="multipart/form-data".

  2. Express server receives the raw HTTP request.

  3. Multer middleware (upload.single('fieldname')) intercepts the request.

  4. Multer reads the boundary and streams each part to:

    • req.body (text fields)

    • Temporary storage (disk or memory)

  5. After parsing, Multer attaches file info to req.file / req.files.

  6. Your route handler receives the populated req object.

  7. You can now:

    • Read file metadata (original name, size, path)

    • Move/rename the file (if disk storage)

    • Process the file (resize, scan, etc.)

    • Save file reference to database

  8. Finally, send a response back to the client.

Visual flow (text):

Client POST → Express app → Multer middleware → parse multipart data
                                                   ↓
                                            store file (disk/memory)
                                                   ↓
                                            attach to req.file
                                                   ↓
                                  route handler (your code) → response

5. Minimal Storage Example (Disk Storage – No Cloud)

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

const app = express();

// Configure storage (minimal disk storage)
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/'); // folder must exist
  },
  filename: (req, file, cb) => {
    // Keep original name (not recommended for prod, but simple for demo)
    cb(null, Date.now() + '-' + file.originalname);
  }
});

const upload = multer({ storage: storage });

// Single file upload route
app.post('/upload', upload.single('myFile'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded');
  }
  res.json({
    message: 'File uploaded successfully',
    file: req.file
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));

HTML form example:

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

Note: This stores files locally. For production, you would later replace diskStorage with cloud storage or a dedicated file server.

6. Diagram Ideas

6.1 Client → Server → Storage Upload Flow

+--------+     1. POST /upload (multipart)      +----------+
|        | -----------------------------------> |          |
| Client |                                      | Node.js  |
|        | <----------------------------------- | Express  |
+--------+     5. JSON response (success/error) | + Multer |
                                                +----------+
                                                     |
                                                     | 2. Parse & stream
                                                     v
                                                +----------+
                                                | Temp     |
                                                | Storage  |
                                                | (disk)   |
                                                +----------+
                                                     |
                                                     | 3. Write file
                                                     v
                                                +----------+
                                                | Permanent|
                                                | Storage  |
                                                | (uploads/)|
                                                +----------+

6.2 Multer Middleware Execution Flow (Inside Express)

Request → Express app
            │
            ▼
      Multer middleware (upload.single)
            │
            ├─► Read boundary & headers
            │
            ├─► For each part:
            │      ├─► Field? → req.body[field] = value
            │      └─► File?  → write to disk/memory, store metadata
            │
            ├─► On error → call next(error)
            │
            └─► On success → attach req.file / req.files
                              │
                              ▼
                        Your route handler
                              │
                              ▼
                        Send response

Alternative diagram (sequence style):

Client          Multer Middleware         Disk Storage       Your Handler
  │                    │                        │                  │
  │ POST /upload       │                        │                  │
  │───────────────────>│                        │                  │
  │                    │                        │                  │
  │                    │ write file chunks       │                  │
  │                    │───────────────────────>│                  │
  │                    │                        │                  │
  │                    │ (parsing complete)      │                  │
  │                    │                        │                  │
  │                    │ attach req.file         │                  │
  │                    │────────────────────────────────────────>   │
  │                    │                        │                  │
  │                    │                        │     res.send()   │
  │<─────────────────────────────────────────────────────────────────│

Summary Table

Topic Key takeaway
What is Multer Middleware to parse multipart/form-data in Express
Multipart explained Splits request into parts – each part is a field or file
Lifecycle Request → Multer parses → attaches to req.file → your code handles
Storage Use diskStorage for local files; avoid cloud in beginner examples
Best suggestions Validate file type, limit size, handle errors, don’t expose raw uploads

Start with the minimal disk example above, then explore memoryStorage (for processing before saving) and later cloud integrations (like Multer-S3).


3. Handling single file upload

Regular HTML forms send data as key=value pairs (application/x-www-form-urlencoded).
But files contain binary data – they cannot be encoded that way.

multipart/form-data splits the HTTP request body into separate parts, each with:

  • A Content-Disposition header (field name, and filename for file parts)

  • Its own content type (e.g., image/jpeg)

  • The actual data (text or binary)

A simplified multipart request looks like:

---------------------------boundary
Content-Disposition: form-data; name="username"

john_doe
---------------------------boundary
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

(binary image data...)
---------------------------boundary--

Multer parses this complex structure so you can simply access req.file and req.body in your route.

2. Upload Lifecycle (Single File)

Browser sends POST (multipart)
        │
        ▼
Express receives request
        │
        ▼
Multer middleware (upload.single('fieldName'))
        │
        ├─ Reads boundaries and streams parts
        ├─ Writes file to disk (or memory)
        ├─ Populates req.body with text fields
        └─ Attaches file info to req.file
        │
        ▼
Your route handler executes
        │
        ├─ Check if req.file exists
        ├─ Process file (rename, move, resize, etc.)
        ├─ Save file metadata to database (optional)
        └─ Send response back to client

Key point: Multer does not automatically save the file to a final location – it uses a temporary destination (or memory). You can define where and how to store it.

3. Minimal Storage Example (Disk Storage – Single File)

No cloud, no extra dependencies – just Express + Multer.

Setup

npm install express multer
mkdir uploads   # create folder for uploaded files

Code (app.js)

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

const app = express();

// Configure disk storage (minimal)
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');      // files land here
  },
  filename: (req, file, cb) => {
    // Simple unique name: timestamp + original name
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, uniqueSuffix + path.extname(file.originalname));
  }
});

const upload = multer({ storage: storage });

// Single file upload route – field name must match form input name
app.post('/upload', upload.single('myFile'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded.');
  }
  res.json({
    message: 'File uploaded successfully',
    file: {
      originalName: req.file.originalname,
      storedName: req.file.filename,
      size: req.file.size,
      path: req.file.path
    }
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));

HTML form (test with browser or Postman)

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

Note: The folder uploads/ must exist before running the app.
For production, you would later add validation, virus scanning, and possibly cloud storage – but start with this.

Example with fileFilter and size limit:

const upload = multer({
  storage: storage,
  limits: { fileSize: 2 * 1024 * 1024 }, // 2MB
  fileFilter: (req, file, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif/;
    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = allowedTypes.test(file.mimetype);
    if (mimetype && extname) return cb(null, true);
    cb(new Error('Only images are allowed'));
  }
});

5. Diagram Ideas

5.1 Client → Server → Storage Upload Flow (Single File)

+--------+     1. POST /upload (multipart)      +----------+
|        | -----------------------------------> |          |
| Client |                                      | Express  |
|        | <----------------------------------- | + Multer |
+--------+     5. JSON response (success/error) +----------+
                                                +----------+
                                                     |
                                                     | 2. Parse & stream
                                                     v
                                                +----------+
                                                | Temporary|
                                                | Storage  |
                                                | (disk)   |
                                                +----------+
                                                     |
                                                     | 3. Write file
                                                     v
                                                +----------+
                                                | Permanent|
                                                | Storage  |
                                                | (uploads/)|
                                                +----------+

5.2 Multer Middleware Execution Flow (Internal)

Request → Express app
            │
            ▼
      upload.single('myFile')
            │
            ├─► Read multipart boundary
            │
            ├─► For each part:
            │      ├─► Field (text) → req.body[field] = value
            │      └─► File part → stream to disk, collect metadata
            │
            ├─► On error (size, type) → next(error)
            │
            └─► On success → attach req.file {
                    fieldname, originalname, encoding, mimetype,
                    destination, filename, path, size
                }
                              │
                              ▼
                        Your route handler
                              │
                              ▼
                        Send response

5.3 Alternative Sequence Diagram (text-based)

Client         Multer Middleware        Disk Storage        Your Handler
  │                   │                      │                   │
  │ POST /upload      │                      │                   │
  │─────────────────>│                      │                   │
  │                   │ write file chunks    │                   │
  │                   │─────────────────────>│                   │
  │                   │                      │                   │
  │                   │ (parsing complete)   │                   │
  │                   │ attach req.file      │                   │
  │                   │────────────────────────────────────────>│
  │                   │                      │                   │
  │                   │                      │    res.json()     │
  │<─────────────────────────────────────────────────────────────│

Summary

  • Multer = middleware to parse multipart/form-data for file uploads.

  • Single file = use upload.single('fieldName').

  • Minimal storage = diskStorage with destination and filename.

  • Avoid cloud initially – master local disk storage first.

  • Always validate type, size, and rename files for security.

4. Handling multiple file uploads

5. Storage configuration basics

6. Serving uploaded files

Now here's the Multer middleware execution flow — zooming into what happens inside that single middleware step:

Both diagrams are interactive — clicking any node sends a follow-up question to the chat so you can drill deeper into any step.

Diagram 1 shows the full lifecycle: from the browser selecting a file, through multipart/form-data encoding, hitting Express, passing through Multer, and landing on local disk — with the validation branch clearly splitting off into an error path.

Diagram 2 zooms into Multer itself: Busboy parsing the raw stream, the fileFilter callback gate, the size limit check, and finally the DiskStorage engine writing the file before next() is called.