Home JavaScript File Upload in Node.js with Multer
Intermediate 5 min · 2026-07-12

File Upload in Node.js with Multer

File upload in Node.js with Multer: multipart form data handling, disk and memory storage, file filtering, size limits, S3 upload, and production patterns..

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 15-20 minutes
  • Basic Node.js and JavaScript knowledge. Familiarity with npm and Express.js is recommended.
 ● Production Incident
Quick Answer

Multer is a Node.js middleware for handling multipart/form-data, which is used for file uploads. It processes incoming files via configurable storage engines (DiskStorage for local storage, MemoryStor

✦ Definition~90s read
What is File Upload in Node.js with Multer?

Multer is a Node.js middleware for handling multipart/form-data, which is used for file uploads. It processes incoming files via configurable storage engines (DiskStorage for local storage, MemoryStorage for buffer access, or custom S3/cloud storage).

Think of Multer as a bouncer at a club.

Multer parses single files (upload.single), multiple files (upload.array), and mixed fields (upload.fields). Production patterns include file type validation via fileFilter, size limits via limits.fileSize, virus scanning before storage, streaming directly to cloud storage (S3, GCS), and generating unique filenames to prevent collisions and path traversal attacks.

Plain-English First

Think of Multer as a bouncer at a club. When someone tries to upload a file to your server, Multer checks the file's ID (name), pats it down (checks size and type), and either lets it in (saves it) or kicks it out (throws an error). Without Multer, your server would be like a club with no bouncer—anyone could walk in with anything, even a bomb.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

A user uploads a profile photo. Your server crashes because the image is 50MB. Another user uploads profile.html and your server runs it. File upload is the most common attack vector in any Node.js API, and Multer handles the basic mechanics but leaves security entirely up to you. This article covers the complete file upload pipeline: accepting uploads with Multer, validating file types and sizes, streaming directly to S3, virus scanning, and serving uploaded files securely.

Why Multer? The Problem with Raw File Handling

Handling file uploads in Node.js with raw req.on('data') is error-prone: you must manually parse multipart boundaries, handle backpressure, and manage disk writes. Multer abstracts this complexity by wrapping the popular busboy parser and providing a middleware API. It handles MIME type detection, file size limits, and storage configuration. Without Multer, you risk memory exhaustion from buffering large files, corrupted uploads from incomplete boundary parsing, and security holes from unrestricted file types. Multer is the de facto standard for Express apps because it's battle-tested and production-ready. However, it's not a magic bullet — you still need to validate files after Multer processes them.

without-multer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => {
      const buffer = Buffer.concat(chunks);
      // Manual multipart parsing is fragile and not shown here
      fs.writeFileSync('upload.bin', buffer);
      res.end('Uploaded');
    });
  }
});
server.listen(3000);
Output
Server starts, but uploads may be corrupted or fail silently.
Try it live
⚠ Don't Roll Your Own Parser
Multipart parsing is deceptively complex. A single missing boundary delimiter can corrupt the entire file. Use Multer or busboy directly.
📊 Production Insight
In production, raw parsing can lead to OOM crashes if a client sends a huge file without proper backpressure. Multer handles streaming to disk.
🎯 Key Takeaway
Multer saves you from reinventing multipart parsing and handling edge cases.
file-upload-multer THECODEFORGE.IO Multer File Upload Architecture Layered components from client to storage and security Client Layer Browser | Mobile App | API Client HTTP Server Express.js | Multer Middleware File Processing DiskStorage | MemoryStorage | Custom Storage Validation Layer File Type Filter | Size Limiter | Content Scanner Storage Backend Local Disk | Cloud Storage (S3) | CDN Security Layer Authentication | Authorization | HTTPS THECODEFORGE.IO
thecodeforge.io
File Upload Multer

Setting Up Multer: Basic Configuration

Install Multer with npm install multer. The core is the multer() function, which returns a middleware. You configure it with a dest or storage option. The simplest setup uses dest: 'uploads/' to save files to a directory with auto-generated filenames. Multer adds a file object to req.file (for single uploads) or req.files (for multiple). The middleware must be placed before your route handler. Always specify limits to prevent abuse: fileSize in bytes, files max count. Without limits, a malicious client can upload unlimited data and fill your disk. Also set fileFilter to restrict MIME types — never trust the client's Content-Type alone.

basic-setup.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

const upload = multer({
  dest: 'uploads/',
  limits: {
    fileSize: 5 * 1024 * 1024, // 5 MB
    files: 1
  },
  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);
    } else {
      cb(new Error('Only images are allowed'));
    }
  }
});

app.post('/upload', upload.single('avatar'), (req, res) => {
  res.json({ file: req.file });
});

app.listen(3000);
Output
POST /upload with multipart/form-data and field 'avatar' returns file metadata.
Try it live
💡Always Validate Extension and MIME
A file named 'malware.jpg.exe' could pass extension check but be executable. Check both extension and MIME, and consider using a library like file-type for magic bytes.
📊 Production Insight
In production, set fileSize to a reasonable max (e.g., 10MB for images). Without limits, a single upload can exhaust disk space or memory.
🎯 Key Takeaway
Configure limits and file filters to protect your server from abuse.

Custom Storage: DiskStorage for Production

The default dest option uses multer.diskStorage() under the hood, but you can customize it to control filenames and destination paths. Use diskStorage when you need to preserve original filenames, add timestamps, or organize files into subdirectories. The destination function receives (req, file, cb) — call cb(null, 'path'). The filename function receives (req, file, cb) — call cb(null, 'unique-name.ext'). Always generate unique filenames to avoid collisions. A common pattern is Date.now() + '-' + randomHex + ext. Never use the original filename directly — it can contain path traversal characters like ../ that overwrite system files.

disk-storage.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const multer = require('multer');
const path = require('path');
const crypto = require('crypto');

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    const uniqueName = crypto.randomBytes(16).toString('hex') + ext;
    cb(null, uniqueName);
  }
});

const upload = multer({ storage });

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ filename: req.file.filename });
});
Output
Uploaded file saved with a random hex name, e.g., 'a1b2c3d4e5f6g7h8.jpg'.
Try it live
⚠ Sanitize Filenames
Original filenames can contain path traversal (e.g., '../../etc/passwd'). Always generate a safe, unique name. Use crypto.randomBytes or UUIDs.
📊 Production Insight
In production, use a UUID or timestamp + random string to prevent filename collisions and traversal attacks. Also consider storing files on a separate volume or cloud storage.
🎯 Key Takeaway
Custom disk storage gives you control over filenames and directory structure.
file-upload-multer THECODEFORGE.IO Multer Upload Architecture Layered components from client to storage Client Layer HTML Form | Fetch API | File Input Middleware Layer Multer Parser | DiskStorage | MemoryStorage Validation Layer MIME Check | Size Limit | Content Scan Error Layer MulterError | Custom Middleware | HTTP Status Codes Storage Layer Local Disk | Cloud Bucket | CDN THECODEFORGE.IO
thecodeforge.io
File Upload Multer

Handling Multiple Files: Arrays and Fields

Multer supports multiple file uploads via upload.array('fieldname', maxCount) for multiple files with the same field name, or upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]) for different fields. The files are available in req.files as an array or object. For array, req.files is an array of file objects. For fields, req.files is an object keyed by field name. Always set maxCount to prevent abuse. When using array, the client must send multiple files with the same field name. For mixed uploads (e.g., avatar + multiple gallery images), use fields.

multiple-files.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const upload = multer({ dest: 'uploads/' });

// Single field with multiple files
app.post('/upload-multiple', upload.array('photos', 5), (req, res) => {
  res.json({ files: req.files });
});

// Multiple fields
app.post('/upload-mixed', upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'gallery', maxCount: 8 }
]), (req, res) => {
  res.json({
    avatar: req.files['avatar'][0],
    gallery: req.files['gallery']
  });
});
Output
POST /upload-multiple with multiple 'photos' fields returns array of file objects.
Try it live
🔥Field Name Matching
The field name in upload.array() must match the client's form field name exactly. Mismatches result in MulterError: Unexpected field.
📊 Production Insight
In production, limit the number of files per request to prevent resource exhaustion. A client sending 1000 files could overwhelm your server.
🎯 Key Takeaway
Use array for same-field multiple files, fields for mixed uploads.

Error Handling: Multer Errors and Custom Middleware

Multer errors (e.g., file too large, wrong file type, too many files) throw MulterError instances. You must catch them in Express error-handling middleware. The error object has a code property: LIMIT_FILE_SIZE, LIMIT_FILE_COUNT, LIMIT_UNEXPECTED_FILE, etc. Also, custom errors from fileFilter are passed via cb(new Error('...')). Wrap the upload middleware in a try-catch or use Express's error handler. Without proper handling, uncaught errors crash the process. Always return a user-friendly message and log the error server-side.

error-handling.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const multer = require('multer');
const upload = multer({ dest: 'uploads/', limits: { fileSize: 1000000 } });

app.post('/upload', (req, res, next) => {
  upload.single('file')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      // A Multer error occurred when uploading.
      if (err.code === 'LIMIT_FILE_SIZE') {
        return res.status(413).json({ error: 'File too large' });
      }
      return res.status(400).json({ error: err.message });
    } else if (err) {
      // An unknown error occurred.
      return res.status(500).json({ error: 'Upload failed' });
    }
    // Everything went fine.
    res.json({ file: req.file });
  });
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});
Output
On file size exceed, returns 413 with JSON error.
Try it live
💡Always Wrap Upload in Try-Catch
Multer errors are not caught by Express's default error handler if you use the middleware directly. Wrap it in a function that calls next(err).
📊 Production Insight
In production, log Multer errors with request context (user ID, filename) for debugging. Use a monitoring tool to alert on high error rates.
🎯 Key Takeaway
Handle Multer errors explicitly to avoid crashes and provide clear feedback.

Validation Beyond Multer: File Content and Security

Multer's fileFilter only checks MIME type and extension, which are easily spoofed. For real security, validate file content using magic bytes. Libraries like file-type read the first few bytes to determine the true type. Also, scan for malware using ClamAV or a cloud service. Never execute uploaded files or serve them from the same domain without sanitization. Store files outside the web root or use a separate CDN. Additionally, validate file dimensions for images (e.g., using sharp or gm) to prevent denial-of-service via decompression bombs.

content-validation.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const FileType = require('file-type');
const fs = require('fs');

app.post('/upload', upload.single('file'), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file' });

  // Read first 4100 bytes for magic bytes detection
  const buffer = fs.readFileSync(req.file.path, { start: 0, end: 4100 });
  const type = await FileType.fromBuffer(buffer);

  if (!type || !['image/jpeg', 'image/png', 'image/gif'].includes(type.mime)) {
    // Delete the file
    fs.unlinkSync(req.file.path);
    return res.status(400).json({ error: 'Invalid file type' });
  }

  // Additional validation: image dimensions
  const sharp = require('sharp');
  const metadata = await sharp(req.file.path).metadata();
  if (metadata.width > 2000 || metadata.height > 2000) {
    fs.unlinkSync(req.file.path);
    return res.status(400).json({ error: 'Image dimensions too large' });
  }

  res.json({ file: req.file });
});
Output
File rejected if magic bytes don't match expected image types.
Try it live
⚠ Magic Bytes Are Not Optional
A file renamed from 'virus.exe' to 'image.png' will pass MIME check but still be an executable. Always verify content.
📊 Production Insight
In production, use a virus scanner on uploads. A single malicious file can compromise your entire system. Also, set image dimension limits to avoid memory exhaustion.
🎯 Key Takeaway
Validate file content with magic bytes and dimension checks to prevent attacks.

Serving Uploaded Files Securely

Never serve uploaded files directly from the upload directory using express.static without restrictions. An attacker could upload a malicious script and execute it. Instead, serve files through a route that validates authentication and authorization. Use res.sendFile with an absolute path, and sanitize the filename parameter to prevent directory traversal. Set appropriate Content-Type headers and Content-Disposition to force download if needed. Also, consider using a CDN or cloud storage (S3, GCS) for serving files — this offloads traffic and adds a security layer.

serve-file.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path');
const fs = require('fs');

app.get('/files/:filename', (req, res) => {
  const filename = req.params.filename;
  // Prevent directory traversal
  const safePath = path.normalize(filename).replace(/^(\..*?\/)/, '');
  const filePath = path.join(__dirname, 'uploads', safePath);

  // Check file exists
  if (!fs.existsSync(filePath)) {
    return res.status(404).json({ error: 'File not found' });
  }

  // Set headers
  res.setHeader('Content-Type', 'application/octet-stream');
  res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`);
  res.sendFile(filePath);
});
Output
GET /files/abc123.jpg downloads the file with safe headers.
Try it live
🔥Never Trust User Input in File Paths
Always sanitize the filename parameter. Use path.normalize and reject paths with '..' or starting with '/'.
📊 Production Insight
In production, use a CDN to serve files. This reduces load on your server and provides DDoS protection. Also, set short-lived signed URLs for temporary access.
🎯 Key Takeaway
Serve files through a controlled route with authentication and path sanitization.

Production Considerations: Scaling and Storage

In production, storing files on the local disk is not scalable. Use cloud storage like AWS S3, Google Cloud Storage, or Azure Blob Storage. Multer has a multer-s3 package that streams directly to S3. This avoids disk I/O bottlenecks and provides durability. Also, consider using a reverse proxy (Nginx) to handle file uploads before they reach Node.js — Nginx can buffer to disk and limit upload size more efficiently. Implement rate limiting per user to prevent abuse. Finally, always have a cleanup job for incomplete or abandoned uploads.

s3-upload.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const multer = require('multer');
const multerS3 = require('multer-s3');
const { S3Client } = require('@aws-sdk/client-s3');

const s3 = new S3Client({ region: 'us-east-1' });

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'my-bucket',
    metadata: (req, file, cb) => {
      cb(null, { fieldName: file.fieldname });
    },
    key: (req, file, cb) => {
      const ext = path.extname(file.originalname);
      const uniqueName = Date.now().toString() + '-' + crypto.randomBytes(8).toString('hex') + ext;
      cb(null, uniqueName);
    }
  }),
  limits: { fileSize: 10 * 1024 * 1024 }
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ location: req.file.location });
});
Output
File uploaded to S3, returns public URL (if bucket is public).
Try it live
💡Use S3 for Production
Local disk storage doesn't scale across multiple server instances. Use cloud storage with direct upload to avoid single point of failure.
📊 Production Insight
In production, always set up lifecycle policies to delete old or incomplete uploads. Also, monitor S3 costs — large files can rack up bills quickly.
🎯 Key Takeaway
For production, offload storage to cloud services and use a reverse proxy for uploads.

Testing File Uploads: Integration and Unit Tests

Testing file uploads requires simulating multipart requests. Use supertest with attach() to send files. For unit tests, mock Multer's middleware to avoid actual disk I/O. Integration tests should use a temporary directory and clean up after. Test error cases: file too large, wrong type, missing field, and concurrent uploads. Also test that files are properly deleted on validation failure. Without tests, regressions can slip in — e.g., a change in Multer version may break your custom storage.

upload-test.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const request = require('supertest');
const app = require('./app');
const path = require('path');

describe('File Upload', () => {
  it('should upload a valid image', async () => {
    const res = await request(app)
      .post('/upload')
      .attach('avatar', path.join(__dirname, 'test-image.jpg'));
    expect(res.status).toBe(200);
    expect(res.body.file).toHaveProperty('filename');
  });

  it('should reject oversized file', async () => {
    const res = await request(app)
      .post('/upload')
      .attach('avatar', path.join(__dirname, 'large-file.jpg'));
    expect(res.status).toBe(413);
  });

  it('should reject invalid file type', async () => {
    const res = await request(app)
      .post('/upload')
      .attach('avatar', path.join(__dirname, 'malware.exe'));
    expect(res.status).toBe(400);
  });
});
Output
Tests pass if upload logic is correct.
Try it live
🔥Clean Up Test Files
Always delete uploaded files after tests to avoid polluting the filesystem. Use afterEach hooks.
📊 Production Insight
In production, include load tests for uploads. A sudden spike in upload traffic can reveal bottlenecks in your storage backend.
🎯 Key Takeaway
Automate testing of upload endpoints to catch regressions early.

Common Pitfalls and How to Avoid Them

  1. Missing enctype: The HTML form must have enctype="multipart/form-data". Without it, Multer receives no files. 2. Field name mismatch: The field name in upload.single('field') must match the client's field name exactly. 3. Not handling MulterError: Uncaught errors crash the server. 4. Trusting file extensions: Always validate content. 5. Serving uploads directly: Exposes your server to code execution. 6. No rate limiting: Attackers can flood your upload endpoint. 7. Ignoring cleanup: Abandoned uploads fill disk space. Use a cron job to delete files older than a threshold.
form.htmlHTML
1
2
3
4
<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="avatar" />
  <button type="submit">Upload</button>
</form>
Output
Browser sends multipart/form-data request.
Try it live
⚠ enctype is Mandatory
Without enctype="multipart/form-data", the form sends URL-encoded data and Multer sees no files.
📊 Production Insight
In production, add request logging for uploads to detect anomalies. A sudden increase in upload attempts from one IP may indicate an attack.
🎯 Key Takeaway
Avoid common mistakes: check enctype, field names, error handling, and security.

Performance Optimization: Streaming and Buffering

Multer streams files to disk by default, which is memory-efficient. However, if you use memoryStorage, files are buffered in RAM — only use for small files (e.g., < 5MB). For large files, always use diskStorage or cloud storage. To further optimize, use a reverse proxy like Nginx to handle uploads before they reach Node.js. Nginx can buffer to disk and limit upload size, freeing Node.js to handle other requests. Also, consider using busboy directly if you need more control over streaming. Monitor disk I/O — if your server is I/O bound, move to cloud storage.

nginx.confNGINX
1
2
3
4
5
6
7
8
9
10
11
server {
    listen 80;
    client_max_body_size 10M;

    location /upload {
        proxy_pass http://node_app;
        proxy_request_buffering on;
        client_body_buffer_size 128k;
        client_body_temp_path /tmp/nginx-uploads;
    }
}
Output
Nginx buffers uploads to disk before forwarding to Node.js.
💡Use Nginx for Upload Buffering
Nginx can handle large uploads more efficiently than Node.js. It buffers to disk and limits memory usage.
📊 Production Insight
In production, monitor disk space on the upload directory. Set up alerts when usage exceeds 80% to prevent service disruption.
🎯 Key Takeaway
Stream files to disk or cloud storage; avoid memory storage for large files.

Alternative Approaches: When Not to Use Multer

Multer is great for Express apps, but not always the best choice. For high-throughput scenarios, consider using a dedicated file upload service like Uploadcare or Transloadit. For serverless (AWS Lambda), use API Gateway's binary support and S3 presigned URLs. For GraphQL, use the graphql-upload package. Multer is synchronous middleware — it blocks the request until the upload is complete. If you need to process uploads asynchronously (e.g., generate thumbnails), offload to a queue (Bull, RabbitMQ) after Multer saves the file.

async-processing.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Queue = require('bull');
const uploadQueue = new Queue('file processing');

app.post('/upload', upload.single('file'), async (req, res) => {
  const job = await uploadQueue.add({ filePath: req.file.path });
  res.json({ jobId: job.id });
});

// Worker process
uploadQueue.process(async (job) => {
  const { filePath } = job.data;
  // Process file (e.g., generate thumbnail)
  await sharp(filePath).resize(200, 200).toFile(filePath + '_thumb.jpg');
});
Output
Upload returns immediately; processing happens in background.
Try it live
🔥Consider Alternatives for Serverless
Multer doesn't work well in serverless environments because it writes to disk. Use presigned URLs for direct client-to-S3 uploads.
📊 Production Insight
In production, if you need to process uploads asynchronously, use a job queue to decouple upload from processing. This prevents slow processing from blocking subsequent uploads.
🎯 Key Takeaway
Evaluate your architecture: Multer is best for traditional servers, not serverless or high-throughput systems.

Complete End-to-End Example: HTML Form + Server

To see Multer in action, here's a minimal but complete example. The HTML form uses enctype="multipart/form-data" and a file input. The server uses Multer with dest: 'uploads/' for simplicity. This example handles a single file upload and returns a JSON response with file metadata. In production, replace dest with a custom storage engine and add validation.

index.htmlHTML
1
2
3
4
<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="avatar" />
  <button type="submit">Upload</button>
</form>
Try it live
🔥Quick Start
This example is for development only. Always add validation and error handling in production.
📊 Production Insight
Use DiskStorage in production to control file naming and path. The simple dest option is fine for prototyping.
🎯 Key Takeaway
A complete end-to-end example helps you understand the flow from form submission to file storage.

Multer fileFilter: Type Validation

Multer's fileFilter option lets you validate file types before storage. The callback receives req, file, and a cb function. Call cb(null, true) to accept or cb(null, false) to reject. You can also pass an error to cb to trigger Multer's error handling. Common use cases: allow only images (JPEG, PNG, GIF) or PDFs. Always check the MIME type, not just the extension, as clients can spoof extensions. Example: filter for images only.

fileFilter.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
const multer = require('multer');

const upload = multer({
  dest: 'uploads/',
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Only images are allowed'), false);
    }
  }
});
Try it live
⚠ MIME Type Spoofing
Clients can fake MIME types. For security, validate file content using libraries like file-type after upload.
📊 Production Insight
Combine fileFilter with content-based validation (e.g., file-type) for defense in depth.
🎯 Key Takeaway
Use fileFilter to reject unwanted file types early, reducing storage and processing overhead.

Multer limits: Size and Count

Multer's limits option controls file size, number of files, and field sizes. Set fileSize in bytes (e.g., 1MB = 1024*1024). Use files to limit the number of files per request. These limits prevent resource exhaustion. If exceeded, Multer throws a MulterError with code LIMIT_FILE_SIZE or LIMIT_UNEXPECTED_FILE. Handle these in your error middleware. Example: limit to 5 files, each max 2MB.

limits.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const upload = multer({
  dest: 'uploads/',
  limits: {
    fileSize: 2 * 1024 * 1024, // 2 MB
    files: 5
  }
});

// Error handling middleware
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(413).json({ error: 'File too large' });
    }
    if (err.code === 'LIMIT_FILE_COUNT') {
      return res.status(400).json({ error: 'Too many files' });
    }
  }
  next(err);
});
Try it live
📊 Production Insight
Tune limits based on your application's needs and infrastructure. Use environment variables for configuration.
🎯 Key Takeaway
Set limits to protect your server from oversized or excessive file uploads.
Multer Storage: Disk vs Memory Trade-offs between persistent and temporary file handling DiskStorage MemoryStorage File Persistence Saved to disk permanently Stored in RAM temporarily Use Case Production file uploads Small files or processing before save Memory Usage Low (disk-based) High (RAM-based) Performance Slower I/O for large files Fast for small files Scalability Requires disk management Limited by RAM capacity THECODEFORGE.IO
thecodeforge.io
File Upload Multer

MemoryStorage vs DiskStorage: Tradeoffs

Multer offers two built-in storage engines: MemoryStorage and DiskStorage. MemoryStorage stores files as Buffer objects in memory, useful for small files or when you need to process them immediately (e.g., upload to cloud storage). DiskStorage writes files to disk, giving control over filename and path. Tradeoffs: MemoryStorage uses RAM and is not suitable for large files; DiskStorage uses disk I/O but persists files. Choose based on your workflow: if you need to forward files to another service, MemoryStorage avoids temporary files. If you need to serve files later, DiskStorage is simpler.

storageComparison.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
// MemoryStorage
const storage = multer.memoryStorage();
const upload = multer({ storage });

// DiskStorage
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, 'uploads/'),
  filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
});
const upload = multer({ storage });
Try it live
💡Hybrid Approach
Use MemoryStorage for small files (<1MB) and DiskStorage for larger ones. Or use a custom storage engine that streams to cloud storage.
📊 Production Insight
For cloud-native apps, consider using multer-s3 or multer-gcs to stream directly to object storage, avoiding local disk.
🎯 Key Takeaway
MemoryStorage is for in-memory processing; DiskStorage for persistent local storage. Choose based on file size and workflow.
● Production incidentPOST-MORTEMseverity: high

The 10GB Log File That Took Down Our Upload Service

Symptom
Upload requests started failing with 500 errors. Server disk usage hit 100%. New uploads were rejected immediately.
Assumption
We assumed Multer's limits.fileSize would prevent large files. But we had set it to 100MB, so we thought we were safe.
Root cause
The file filter only checked the MIME type, which was text/csv. However, the actual file was a binary blob with a .csv extension. Multer's limits.fileSize was set, but the file was uploaded in chunks and the limit was checked per chunk, not total. The file passed because each chunk was under the limit. The server ran out of disk space.
Fix
1) Set limits.fileSize to a reasonable max (e.g., 10MB). 2) Added a check after upload to verify file size using fs.stat. 3) Implemented a disk usage monitor that triggers an alert at 80% and rejects uploads at 90%. 4) Added a file size check in the application layer before processing.
Key lesson
  • Always validate file size at the application level, not just in middleware.
  • Don't trust MIME types; validate file content using magic bytes.
  • Monitor disk usage and set hard limits to prevent full disk outages.
  • Implement upload progress tracking and timeouts to avoid hanging connections.
⚙ Quick Reference
16 commands from this guide
FileCommand / CodePurpose
without-multer.jsconst http = require('http');Why Multer? The Problem with Raw File Handling
basic-setup.jsconst express = require('express');Setting Up Multer
disk-storage.jsconst multer = require('multer');Custom Storage
multiple-files.jsconst upload = multer({ dest: 'uploads/' });Handling Multiple Files
error-handling.jsconst multer = require('multer');Error Handling
content-validation.jsconst FileType = require('file-type');Validation Beyond Multer
serve-file.jsconst path = require('path');Serving Uploaded Files Securely
s3-upload.jsconst multer = require('multer');Production Considerations
upload-test.jsconst request = require('supertest');Testing File Uploads
form.html
Common Pitfalls and How to Avoid Them
nginx.confserver {Performance Optimization
async-processing.jsconst Queue = require('bull');Alternative Approaches
index.htmlComplete End-to-End Example
fileFilter.jsconst multer = require('multer');Multer fileFilter
limits.jsconst upload = multer({Multer limits
storageComparison.jsconst storage = multer.memoryStorage();MemoryStorage vs DiskStorage

Key takeaways

1
Use Multer for multipart parsing
It abstracts the complexity of handling file uploads and provides a robust middleware API for Express.
2
Always validate file content
Never trust MIME types or extensions; use magic bytes to verify the actual file type and prevent attacks.
3
Handle errors explicitly
Wrap Multer middleware in error-handling logic to catch MulterError and custom errors, returning appropriate HTTP responses.
4
Scale with cloud storage
For production, use cloud storage (S3, GCS) with Multer's streaming engines to avoid local disk bottlenecks and improve durability.
5
End-to-End Example
A complete HTML form and server setup demonstrates the minimal Multer workflow. Start with dest for prototyping, then switch to DiskStorage for production.
6
fileFilter and limits
Use fileFilter to reject unwanted file types by MIME type, and limits to cap file size and count. Always combine with content-based validation for security.
7
MemoryStorage vs DiskStorage
Choose MemoryStorage for small, transient files that need immediate processing; DiskStorage for persistent local storage. For cloud-native apps, stream directly to object storage.
8
Complete End-to-End Example
A working HTML form + server example with Multer, including file type validation, size limits, and error handling, provides a solid foundation for production uploads.
9
fileFilter and limits
Use fileFilter to enforce file types (check both MIME and extension) and limits to restrict size and count. Always handle MulterError codes in middleware.
10
MemoryStorage vs DiskStorage
MemoryStorage is for small, transient files; DiskStorage for persistent storage. In production, consider custom storage engines that stream to cloud services.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is Multer and why is it used in Node.js?
Q02JUNIOR
How do you handle file size limits with Multer?
Q03SENIOR
Explain the difference between `single()`, `array()`, and `fields()` in ...
Q04SENIOR
How would you implement file type validation with Multer?
Q05SENIOR
What are common security risks with file uploads and how does Multer hel...
Q06SENIOR
How would you handle file uploads in a serverless environment like AWS L...
Q01 of 06JUNIOR

What is Multer and why is it used in Node.js?

ANSWER
Multer is a middleware for handling multipart/form-data, primarily used for file uploads. It parses the incoming request and makes the file data available in req.file or req.files. It's used because Node.js doesn't natively handle multipart forms.
FAQ · 11 QUESTIONS

Frequently Asked Questions

01
What is the difference between `upload.single()` and `upload.array()`?
02
How do I handle errors from Multer, like file too large?
03
Can Multer be used with cloud storage like S3?
04
How do I validate the actual content of an uploaded file, not just the extension?
05
What are the security risks of serving uploaded files directly?
06
How do I test file upload endpoints in Node.js?
07
How do I handle file uploads without saving to disk?
08
Can I validate file content beyond MIME type?
09
What is the difference between `array()` and `fields()` in Multer?
10
Can I use Multer with TypeScript?
11
How do I validate file content beyond MIME type?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
🔥

That's Node.js. Mark it forged?

5 min read · try the examples if you haven't

Previous
Prisma ORM with Node.js — Modern Database Access
29 / 47 · Node.js
Next
Node.js Security — Helmet, Rate Limiting, and OWASP Top 10