# Handling File Uploads in Express with Multer


 File uploads are tricky. The browser sends files in a special format, your server needs to parse them, and you need to store them safely. Multer handles all of this automatically with just a few lines of code.

This is about understanding how file uploads work and building upload systems with Express and Multer.

* * *

## Why File Uploads Need Middleware

When a user uploads a file, the browser doesn't send it like normal form data.

### Regular Form Data

```javascript
// Normal form data
const data = {
  name: "Alice",
  email: "alice@example.com"
};

// Sent as URL-encoded
name=Alice&email=alice@example.com
```

Express can parse this automatically with `express.urlencoded()`.

### File Uploads Are Different

Files are binary data. They can't be sent as simple text like `name=Alice&email=alice@example.com`.

The browser uses a special format called **multipart/form-data**.

### What is Multipart/Form-Data?

Think of it like a package with multiple items:

```plaintext
------boundary123456
Content-Disposition: form-data; name="email"

alice@example.com
------boundary123456
Content-Disposition: form-data; name="file"; filename="resume.pdf"
Content-Type: application/pdf

[binary file data here]
------boundary123456--
```

It's a special format that separates form fields and files with boundaries.

### Why You Need Middleware

Express by itself cannot:

- Parse multipart/form-data
- Extract files from the request
- Save files to disk
- Handle file streams safely

Without middleware, you'd have to:

```javascript
// Without middleware: painful
app.post("/upload", (req, res) => {
  // Manually parse multipart boundaries
  // Manually extract binary data
  // Manually save to disk
  // Manually handle errors
  // 100+ lines of code
});
```

With Multer:

```javascript
// With Multer: simple
app.post("/upload", multer({ dest: "uploads/" }).single("file"), (req, res) => {
  // File is already saved
  res.json({ message: "File uploaded" });
});
```

That's why you need middleware for file uploads.

* * *

## What is Multer?

Multer is Express middleware that handles multipart/form-data.

### Simple Definition

Multer is a library that:

- Parses incoming file uploads
- Extracts files from requests
- Saves files to your server
- Provides file information to your route

It sits between the client and your route handler.

### Real Example

```plaintext
Client sends file
      |
      v
Multer receives multipart/form-data
      |
      v
Multer parses the boundaries
      |
      v
Multer extracts the file
      |
      v
Multer saves to disk (or memory)
      |
      v
Your route handler gets req.file
      |
      v
You send response
```

### Why Multer

Before Multer, developers had to:

- Manually parse multipart boundaries
- Handle file streams
- Validate file types
- Manage storage locations

Multer automates all of this.

### Installation

```bash
npm install multer
```

* * *

## Multer Middleware Execution Flow

```plaintext
Request arrives with file
      |
      v
Multer checks Content-Type
(must be multipart/form-data)
      |
      v
Multer parses boundaries
      |
      v
Multer extracts file(s)
      |
      v
Multer validates (size, type, etc)
      |
      v
Multer saves to destination
      |
      v
Multer populates req.file or req.files
      |
      v
Route handler executes
```

The middleware does all the heavy lifting before your code runs.

* * *

## Handling Single File Upload

### Basic Setup

```javascript
const express = require("express");
const multer = require("multer");

const app = express();

// Configure multer
const upload = multer({ dest: "uploads/" });

// Single file upload route
app.post("/upload", upload.single("file"), (req, res) => {
  // req.file contains the uploaded file
  res.json({
    message: "File uploaded successfully",
    file: req.file
  });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

### What Happens

1. Client sends file in form field named "file"
2. Multer intercepts the request
3. Multer saves file to `uploads/` directory
4. Multer adds `req.file` object to the request
5. Route handler accesses `req.file`

### The req.file Object

```javascript
app.post("/upload", upload.single("file"), (req, res) => {
  console.log(req.file);
  // Outputs:
  // {
  //   fieldname: "file",          // Form field name
  //   originalname: "resume.pdf",  // Original filename
  //   encoding: "7bit",            // File encoding
  //   mimetype: "application/pdf",  // File type
  //   destination: "uploads/",     // Where it was saved
  //   filename: "abc123def456",    // Saved filename (random)
  //   path: "uploads/abc123def456", // Full path
  //   size: 45678                  // File size in bytes
  // }
  
  res.json({
    filename: req.file.filename,
    size: req.file.size,
    mimetype: req.file.mimetype
  });
});
```

### HTML Form for Upload

```html
<!DOCTYPE html>
<html>
<body>
  <form action="http://localhost:3000/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="file" required>
    <button type="submit">Upload</button>
  </form>
</body>
</html>
```

Important: The `enctype="multipart/form-data"` is required. Without it, the file isn't sent correctly.

### Test With curl

```bash
curl -X POST \
  -F "file=@resume.pdf" \
  http://localhost:3000/upload
```

The `-F` flag tells curl to send as multipart/form-data.

* * *

## Handling Multiple File Uploads

### Multiple Files, Single Field

Upload several files using the same form field:

```javascript
const express = require("express");
const multer = require("multer");

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

app.post("/upload-multiple", upload.array("files", 10), (req, res) => {
  // req.files is an array of file objects
  res.json({
    message: "Files uploaded",
    count: req.files.length,
    files: req.files.map(f => ({
      filename: f.filename,
      size: f.size
    }))
  });
});

app.listen(3000);
```

### What Changed

`.single("file")` becomes `.array("files", 10)`.

The second argument is the maximum number of files allowed. This prevents abuse.

### req.files Array

```javascript
app.post("/upload-multiple", upload.array("files", 10), (req, res) => {
  console.log(req.files);
  // Outputs array of file objects:
  // [
  //   { fieldname: "files", originalname: "doc1.pdf", ... },
  //   { fieldname: "files", originalname: "doc2.pdf", ... },
  //   { fieldname: "files", originalname: "doc3.pdf", ... }
  // ]
  
  req.files.forEach(file => {
    console.log(`${file.originalname} (${file.size} bytes)`);
  });
});
```

### HTML Form for Multiple Files

```html
<form action="http://localhost:3000/upload-multiple" method="POST" enctype="multipart/form-data">
  <input type="file" name="files" multiple required>
  <button type="submit">Upload Files</button>
</form>
```

The `multiple` attribute lets users select multiple files.

### Different Fields, Different Files

Upload files to different form fields:

```javascript
// .fields() takes an array of field configurations
const upload = multer({ dest: "uploads/" });

app.post("/upload-mixed", upload.fields([
  { name: "avatar", maxCount: 1 },
  { name: "documents", maxCount: 5 }
]), (req, res) => {
  console.log(req.files);
  // Outputs:
  // {
  //   avatar: [ { file object } ],
  //   documents: [ { file object }, { file object }, ... ]
  // }
  
  const avatar = req.files.avatar[0];
  const docs = req.files.documents;
  
  res.json({ avatar, docs });
});
```

### HTML Form with Multiple Fields

```html
<form action="http://localhost:3000/upload-mixed" method="POST" enctype="multipart/form-data">
  <label>Profile Picture</label>
  <input type="file" name="avatar" required>
  
  <label>Documents</label>
  <input type="file" name="documents" multiple required>
  
  <button type="submit">Upload</button>
</form>
```

* * *

## Storage Configuration Basics

By default, Multer saves files with random names. You might want to customize this.

### Memory Storage

Store files in RAM instead of disk:

```javascript
const multer = require("multer");

// Memory storage (files in RAM)
const storage = multer.memoryStorage();
const upload = multer({ storage });

app.post("/upload", upload.single("file"), (req, res) => {
  // File is in memory
  console.log(req.file);
  // {
  //   fieldname: "file",
  //   originalname: "resume.pdf",
  //   encoding: "7bit",
  //   mimetype: "application/pdf",
  //   buffer: <Buffer...>,    // File binary data
  //   size: 45678
  // }
  
  // No destination or path (it's in RAM)
  res.json({ message: "File received" });
});
```

Use memory storage when:

- Files are small
- You process them immediately
- You don't need to persist them

Don't use memory storage when:

- Files are large (drains RAM)
- Many users upload simultaneously
- You need to store files permanently

### Disk Storage with Custom Names

Save to disk with meaningful filenames:

```javascript
const multer = require("multer");
const path = require("path");

// Disk storage with custom configuration
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    // Where to save
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    // How to name the file
    // cb(null, originalname) keeps the original name
    // But this is risky (filename injection)
    
    // Better: use timestamp + original extension
    const uniqueName = Date.now() + path.extname(file.originalname);
    cb(null, uniqueName);
  }
});

const upload = multer({ storage });

app.post("/upload", upload.single("file"), (req, res) => {
  console.log(req.file.filename);
  // Output: 1620000000000.pdf
  
  res.json({ filename: req.file.filename });
});
```

### Safe Filename Strategy

Never trust the user's filename. Always generate your own:

```javascript
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    // Option 1: Timestamp + extension
    const filename = Date.now() + path.extname(file.originalname);
    cb(null, filename);
    
    // Option 2: UUID + extension
    // const filename = uuid() + path.extname(file.originalname);
    // cb(null, filename);
    
    // Option 3: User ID + timestamp
    // const filename = req.user.id + "-" + Date.now() + path.extname(file.originalname);
    // cb(null, filename);
  }
});
```

Always append the file extension to preserve file type.

### Complete Storage Example

```javascript
const express = require("express");
const multer = require("multer");
const path = require("path");

const app = express();

// Configure storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    const uniqueName = Date.now() + path.extname(file.originalname);
    cb(null, uniqueName);
  }
});

const upload = multer({
  storage,
  limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max
  fileFilter: (req, file, cb) => {
    // Only allow PDF and images
    const allowedMimes = ["application/pdf", "image/jpeg", "image/png"];
    if (allowedMimes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error("Invalid file type"));
    }
  }
});

app.post("/upload", upload.single("file"), (req, res) => {
  res.json({
    message: "File uploaded",
    filename: req.file.filename,
    size: req.file.size
  });
});

app.listen(3000);
```

Key options:

- `limits.fileSize`: Maximum file size in bytes
- `fileFilter`: Validate file type, name, or size

* * *

## Serving Uploaded Files

After uploading, users need to download or view files.

### Static File Serving

Tell Express to serve files from the uploads directory:

```javascript
const express = require("express");
const multer = require("multer");

const app = express();

// Serve uploaded files as static
app.use("/uploads", express.static("uploads/"));

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname));
  }
});

const upload = multer({ storage });

app.post("/upload", upload.single("file"), (req, res) => {
  const fileUrl = `/uploads/${req.file.filename}`;
  
  res.json({
    message: "File uploaded",
    url: fileUrl  // Client can access at this URL
  });
});

app.listen(3000);
```

Now files are accessible at `http://localhost:3000/uploads/filename`.

### Downloading vs Viewing

In the browser, images display. But PDFs should download:

```javascript
app.get("/download/:filename", (req, res) => {
  const filename = req.params.filename;
  const filepath = path.join(__dirname, "uploads", filename);
  
  // Force download
  res.download(filepath);
});
```

The `.download()` method tells the browser to download instead of display.

### Complete Upload and Download Example

```javascript
const express = require("express");
const multer = require("multer");
const path = require("path");

const app = express();

// Serve uploads folder
app.use("/uploads", express.static("uploads/"));

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname));
  }
});

const upload = multer({ storage });

// Upload endpoint
app.post("/upload", upload.single("file"), (req, res) => {
  res.json({
    message: "File uploaded",
    filename: req.file.filename,
    url: `/uploads/${req.file.filename}`
  });
});

// Download endpoint
app.get("/download/:filename", (req, res) => {
  const filepath = path.join(__dirname, "uploads", req.params.filename);
  res.download(filepath);
});

// View files endpoint
app.get("/files", (req, res) => {
  const fs = require("fs");
  const files = fs.readdirSync("uploads/");
  
  res.json({ files });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

### Test Upload and Download

Upload:

```bash
curl -X POST \
  -F "file=@resume.pdf" \
  http://localhost:3000/upload
```

Response:

```json
{
  "message": "File uploaded",
  "filename": "1620000000000.pdf",
  "url": "/uploads/1620000000000.pdf"
}
```

Download:

```bash
curl -O http://localhost:3000/download/1620000000000.pdf
```

Or in the browser: `http://localhost:3000/uploads/1620000000000.pdf`

* * *

## Client to Server to Storage Upload Lifecycle

```plaintext
User selects file in browser
      |
      v
Form sends POST request
(multipart/form-data format)
      |
      v
Express receives request
      |
      v
Multer middleware intercepts
      |
      v
Multer parses multipart boundaries
      |
      v
Multer extracts file data
      |
      v
Multer validates file
(type, size, filters)
      |
      ├─ Invalid → Error response
      └─ Valid → Continue
      |
      v
Multer saves to disk
(or memory)
      |
      v
Multer populates req.file
      |
      v
Route handler executes
      |
      v
Handler sends response
(with file info/URL)
      |
      v
Browser receives response
      |
      v
User can access file at URL
```

* * *

## Common Multer Options

### File Size Limits

```javascript
const upload = multer({
  storage,
  limits: {
    fileSize: 5 * 1024 * 1024  // 5MB
  }
});
```

Reject files larger than 5MB.

### File Type Validation

```javascript
const upload = multer({
  storage,
  fileFilter: (req, file, cb) => {
    if (file.mimetype === "application/pdf") {
      cb(null, true);  // Accept
    } else {
      cb(new Error("Only PDF files allowed"));  // Reject
    }
  }
});
```

Only allow PDFs. Reject other types.

### Multiple Extensions

```javascript
const upload = multer({
  storage,
  fileFilter: (req, file, cb) => {
    const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
    
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error("Invalid file type"));
    }
  }
});
```

Allow images and PDFs.

### Error Handling

```javascript
app.post("/upload", (req, res) => {
  upload.single("file")(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      // Multer error (file too large, etc)
      return res.status(400).json({ error: err.message });
    } else if (err) {
      // Custom error (from fileFilter)
      return res.status(400).json({ error: err.message });
    }
    
    // Success
    res.json({ message: "File uploaded", file: req.file });
  });
});
```

Catch both Multer errors and custom validation errors.

* * *

## Complete Practical Example

```javascript
const express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");

const app = express();
app.use(express.json());

// Create uploads folder if it doesn't exist
if (!fs.existsSync("uploads/")) {
  fs.mkdirSync("uploads/");
}

// Serve uploaded files
app.use("/uploads", express.static("uploads/"));

// Configure storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    const uniqueName = Date.now() + path.extname(file.originalname);
    cb(null, uniqueName);
  }
});

// Configure upload with validation
const upload = multer({
  storage,
  limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max
  fileFilter: (req, file, cb) => {
    const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error("Only JPEG, PNG, and PDF files are allowed"));
    }
  }
});

// Single file upload
app.post("/upload", (req, res) => {
  upload.single("file")(req, res, (err) => {
    if (err) {
      return res.status(400).json({ error: err.message });
    }
    
    if (!req.file) {
      return res.status(400).json({ error: "No file provided" });
    }
    
    res.json({
      message: "File uploaded successfully",
      file: {
        filename: req.file.filename,
        originalname: req.file.originalname,
        size: req.file.size,
        url: `/uploads/${req.file.filename}`
      }
    });
  });
});

// Multiple files upload
app.post("/upload-multiple", (req, res) => {
  upload.array("files", 10)(req, res, (err) => {
    if (err) {
      return res.status(400).json({ error: err.message });
    }
    
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ error: "No files provided" });
    }
    
    const files = req.files.map(f => ({
      filename: f.filename,
      originalname: f.originalname,
      size: f.size,
      url: `/uploads/${f.filename}`
    }));
    
    res.json({
      message: "Files uploaded successfully",
      count: files.length,
      files
    });
  });
});

// List uploaded files
app.get("/files", (req, res) => {
  fs.readdir("uploads/", (err, files) => {
    if (err) {
      return res.status(500).json({ error: "Could not read files" });
    }
    
    res.json({ files });
  });
});

// Download file
app.get("/download/:filename", (req, res) => {
  const filepath = path.join(__dirname, "uploads", req.params.filename);
  
  // Prevent directory traversal attacks
  if (!filepath.startsWith(path.join(__dirname, "uploads"))) {
    return res.status(403).json({ error: "Forbidden" });
  }
  
  res.download(filepath);
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

Test it:

```bash
# Upload single file
curl -X POST \
  -F "file=@resume.pdf" \
  http://localhost:3000/upload

# Upload multiple files
curl -X POST \
  -F "files=@file1.pdf" \
  -F "files=@file2.pdf" \
  http://localhost:3000/upload-multiple

# List files
curl http://localhost:3000/files

# Download file
curl -O http://localhost:3000/download/1620000000000.pdf
```

* * *

## Practice Assignment

**1. Build a single file upload route:**

```javascript
// Create POST /upload endpoint
// Accept file in "file" form field
// Save to "uploads/" directory
// Return uploaded file info
// Test with curl
```

**2. Add file validation:**

```javascript
// Validate file type (only PDF and images)
// Validate file size (max 2MB)
// Return error if validation fails
// Test with invalid file
```

**3. Create multiple file upload:**

```javascript
// Create POST /upload-multiple endpoint
// Accept up to 5 files in "files" field
// Save all files
// Return array of file info
// Test with multiple files
```

**4. Serve uploaded files:**

```javascript
// Use express.static() for uploads folder
// Create GET /files endpoint to list all files
// Create GET /download/:filename endpoint
// Test accessing and downloading files
```

**5. Add error handling:**

```javascript
// Handle Multer errors (file too large)
// Handle custom validation errors
// Return meaningful error messages
// Test with various error scenarios
```

* * *

## Quick Recap

- **File uploads** are sent as multipart/form-data, a special format the browser uses to send binary data.

- **Middleware** is needed because Express doesn't parse multipart/form-data by default.

- **Multer** is Express middleware that parses multipart/form-data and saves files.

- `.single("field")` uploads one file from form field "field".

- `.array("field", max)` uploads multiple files from the same field.

- `.fields()` uploads different files to different fields.

- **req.file** contains info about a single uploaded file (with `.single()`).

- **req.files** contains info about multiple uploaded files (with `.array()` or `.fields()`).

- **Memory storage** stores files in RAM (good for small, temporary files).

- **Disk storage** saves files to the server filesystem (good for persistent storage).

- Always use custom filenames to prevent **filename injection attacks**.

- **File validation** with `fileFilter` prevents unwanted file types.

- **File size limits** prevent storage abuse.

- `express.static()` serves uploaded files so users can access them.

- `res.download()` forces download instead of viewing in the browser.

- Multipart/form-data requires the HTML form attribute `enctype="multipart/form-data"`.

Multer makes file uploads simple and secure.

* * *

> If you enjoyed this article, check out my other blogs on this profile. Connect with me: [LinkedIn](https://linkedin.com/in/rajharsh03) | [GitHub](https://github.com/RajHarsh03) | [X (Twitter)](https://x.com/rajharsh03)
