Examples of Multer File Uploads in Node.js: 3 Practical Patterns You’ll Actually Use

If you’re building anything in Express that touches user-generated content, you’ll hit file uploads fast. And in Node.js, that usually means Multer. You’re probably not looking for theory; you want **examples of multer file uploads in Node.js: 3 practical examples** that mirror what real apps do in production. That’s what this guide focuses on. We’ll walk through a single-image avatar upload, a multi-file upload for product galleries, and a safer, disk-based upload pipeline that’s ready for cloud storage. Along the way, you’ll see **real examples** of middleware configuration, validation, error handling, and folder structure that you can drop into your own code. Instead of toy snippets that only work in tutorials, these patterns match how modern APIs and dashboards are built in 2024–2025. If you’ve ever wondered which **examples include** filename sanitizing, MIME-type checks, or how to combine Multer with async/await and TypeScript-friendly patterns, keep reading. These are the **best examples** I’d use myself when starting a new Node.js service.
Written by
Jamie
Published

Let’s start with the most common scenario: a user uploads a profile picture. This is the example of Multer usage you’ll see in almost every real-world Express app.

Key requirements in this pattern:

  • Accept a single image file under the avatar field
  • Limit file size (for example, 2 MB)
  • Restrict to safe image types (JPEG, PNG, WebP)
  • Expose the stored filename or URL back to the client

Basic setup: Express + Multer

npm install express multer
// src/server.js
const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// Configure storage
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, path.join(__dirname, 'uploads/avatars'));
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname).toLowerCase();
    const base = path.basename(file.originalname, ext)
      .toLowerCase()
      .replace(/[^a-z0-9]/g, '-');

    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, `\({base}-}\(uniqueSuffix}${ext}`);
  }
});

// File filter for images only
function imageFileFilter(req, file, cb) {
  const allowed = ['image/jpeg', 'image/png', 'image/webp'];
  if (!allowed.includes(file.mimetype)) {
    return cb(new Error('Only JPEG, PNG, and WebP images are allowed'));
  }
  cb(null, true);
}

const upload = multer({
  storage,
  fileFilter: imageFileFilter,
  limits: {
    fileSize: 2 * 1024 * 1024 // 2 MB
  }
});

app.post('/api/users/:id/avatar', upload.single('avatar'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'Avatar file is required' });
  }

  // In a real app, you’d save req.file.filename to the user record
  res.json({
    message: 'Avatar uploaded',
    filename: req.file.filename,
    path: `/uploads/avatars/${req.file.filename}`
  });
});

// Simple error handler for Multer errors
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    return res.status(400).json({ error: err.message });
  }
  if (err) {
    return res.status(400).json({ error: err.message });
  }
  next();
});

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

This first pattern is the baseline for many examples of multer file uploads in Node.js: 3 practical examples you’ll see online. The difference here is that the filename handling and MIME filtering are already production-minded, not just demo code.

Why this pattern still holds up in 2024–2025

Even with the rise of direct-to-cloud uploads (S3, GCS, etc.), most production systems still pass through a Node.js backend at some point for:

  • Authentication and authorization checks
  • Virus scanning or basic validation
  • Generating signed URLs or storing metadata

Multer remains a workhorse for this. The best examples of modern upload flows often start with this simple avatar pattern and then layer in queues, workers, or cloud storage.


Example 2: Multiple file uploads for product galleries and documents

The second pattern in our set of examples of multer file uploads in Node.js: 3 practical examples is a multi-file scenario. Think of an e-commerce admin panel where a seller uploads several product images, plus a PDF spec sheet.

We’ll use upload.fields() to accept different fields with different limits:

  • images: up to 5 image files
  • specSheet: a single PDF

Multi-field Multer configuration

// src/uploads/productUpload.js
const multer = require('multer');
const path = require('path');

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    const isImage = file.mimetype.startsWith('image/');
    const folder = isImage ? 'product-images' : 'product-docs';
    cb(null, path.join(__dirname, `../uploads/${folder}`));
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname).toLowerCase();
    const base = path.basename(file.originalname, ext)
      .toLowerCase()
      .replace(/[^a-z0-9]/g, '-');

    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, `\({base}-}\(uniqueSuffix}${ext}`);
  }
});

function productFileFilter(req, file, cb) {
  if (file.fieldname === 'images') {
    if (!file.mimetype.startsWith('image/')) {
      return cb(new Error('Only image files are allowed for images field'));
    }
  }
  if (file.fieldname === 'specSheet') {
    if (file.mimetype !== 'application/pdf') {
      return cb(new Error('Spec sheet must be a PDF'));
    }
  }
  cb(null, true);
}

const uploadProduct = multer({
  storage,
  fileFilter: productFileFilter,
  limits: {
    fileSize: 5 * 1024 * 1024 // 5 MB per file
  }
});

module.exports = {
  uploadProduct
};

Route using multiple fields

// src/routes/products.js
const express = require('express');
const { uploadProduct } = require('../uploads/productUpload');

const router = express.Router();

const productUploadMiddleware = uploadProduct.fields([
  { name: 'images', maxCount: 5 },
  { name: 'specSheet', maxCount: 1 }
]);

router.post('/products', productUploadMiddleware, async (req, res) => {
  try {
    const { name, price } = req.body;

    const imageFiles = req.files?.images || [];
    const specSheetFiles = req.files?.specSheet || [];

    const imagePaths = imageFiles.map((f) => `/uploads/product-images/${f.filename}`);
    const specSheetPath = specSheetFiles[0]
      ? `/uploads/product-docs/${specSheetFiles[0].filename}`
      : null;

    // Simulate DB save
    const product = {
      id: Date.now().toString(),
      name,
      price: Number(price),
      images: imagePaths,
      specSheet: specSheetPath
    };

    res.status(201).json({
      message: 'Product created',
      product
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to create product' });
  }
});

module.exports = router;

Here you get a more realistic example of Multer in action: different rules per field, different directories, and a route that treats uploads as part of a larger data payload.

Other real examples that mirror this pattern

Modern apps in 2024–2025 use variations of this multi-file pattern all the time. Some examples include:

  • HR portals where candidates upload a resume (PDF) and portfolio images
  • Learning platforms where instructors upload a thumbnail plus multiple lecture resources
  • Legal or compliance tools that accept a single signed document and supporting images

All of these are examples of multer file uploads in Node.js: 3 practical examples extended into actual product requirements.


Example 3: Safer, disk-based uploads as a step toward cloud storage

The third pattern brings Multer closer to how teams deploy Node.js in production in 2024–2025. Instead of treating the upload directory as the final destination, you treat it as a temporary staging area.

This is one of the best examples for teams planning to move files to S3, Google Cloud Storage, or Azure Blob Storage.

Why stage uploads on disk first?

You get a chance to:

  • Run virus scans or content checks using a separate process
  • Generate thumbnails or optimized versions
  • Enforce retention policies (for example, purge temp files nightly)

For security guidance around handling uploaded content and scanning, the general principles outlined in the US-CERT secure coding recommendations and the OWASP community at owasp.org are worth reading, even though they’re not Node-specific.

Multer config for temporary storage

// src/uploads/tempUpload.js
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const tempDir = path.join(__dirname, '../uploads/tmp');
if (!fs.existsSync(tempDir)) {
  fs.mkdirSync(tempDir, { recursive: true });
}

const tempStorage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, tempDir);
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname).toLowerCase();
    const base = path.basename(file.originalname, ext)
      .toLowerCase()
      .replace(/[^a-z0-9]/g, '-');

    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, `\({base}-}\(uniqueSuffix}${ext}`);
  }
});

const uploadTemp = multer({
  storage: tempStorage,
  limits: {
    fileSize: 20 * 1024 * 1024 // 20 MB
  }
});

module.exports = { uploadTemp };

Route that hands off to an async processor

// src/routes/uploads.js
const express = require('express');
const fs = require('fs/promises');
const path = require('path');
const { uploadTemp } = require('../uploads/tempUpload');

const router = express.Router();

router.post('/uploads/raw', uploadTemp.single('file'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'File is required' });
  }

  const tempPath = req.file.path;

  // Simulate async processing: move to a permanent folder
  const finalDir = path.join(__dirname, '../uploads/final');
  await fs.mkdir(finalDir, { recursive: true });

  const finalPath = path.join(finalDir, req.file.filename);
  await fs.rename(tempPath, finalPath);

  res.status(201).json({
    message: 'File uploaded and processed',
    filename: req.file.filename,
    path: `/uploads/final/${req.file.filename}`
  });
});

module.exports = router;

This pattern is a stepping stone to full-blown cloud uploads. Swap out the fs.rename call for an S3 client upload, and you have a production-ready pipeline.

In other words, these three patterns are not just toy examples of multer file uploads in Node.js: 3 practical examples; they’re realistic building blocks for modern microservices and monoliths alike.


Six more real-world ways developers use these Multer patterns

To go beyond the original three, here are additional real examples that extend directly from the code above:

  • A messaging app that lets users upload a single image or video per message, using a variant of the avatar route but with different size limits
  • A medical intake portal where patients upload insurance cards and lab PDFs; the multi-field pattern applies here, and content is later scanned according to internal security policies and healthcare guidelines from organizations like NIH and Mayo Clinic
  • A learning management system that stages lecture recordings using the temp-storage pattern, then kicks off a background job to transcode video and extract captions
  • A bug-reporting dashboard where users attach screenshots and log files; again, multi-field uploads with different MIME rules
  • A design feedback tool that stores original high-res assets with the temp pattern and then generates optimized web previews
  • A compliance archive that treats uploaded documents as write-once and moves them from the temp folder into immutable storage

All of these are examples of how the same three Multer patterns show up in real products.


Best practices that show up across the best examples

Across these examples of multer file uploads in Node.js: 3 practical examples, some patterns repeat:

  • Always validate MIME type and size in Multer, and if needed, validate again in your business logic
  • Never trust the original filename; generate your own safe version
  • Treat local disk as either a cache or a temp buffer, not your long-term storage strategy
  • Keep upload middleware small and focused; business logic belongs in separate services or layers

For broader security thinking, the OWASP community at owasp.org is still the go-to reference, and US government cyber guidance from CISA/US-CERT is a good sanity check, especially if you’re working in regulated sectors.


FAQ: Multer file uploads in Node.js

What are some real examples of Multer usage in production apps?

Real examples of Multer usage include avatar uploads, product galleries, document submission portals, HR systems that handle resumes and portfolios, and admin dashboards for uploading marketing assets. The three patterns in this guide map almost one-to-one to those cases.

Can you show an example of limiting file size and type with Multer?

Yes. In the avatar upload example, the Multer config uses limits.fileSize and a fileFilter that checks file.mimetype. That example of configuration is usually enough for basic protection, and you can reuse it across routes.

How do I handle errors from these examples of Multer file uploads in Node.js?

Wrap your routes with try/catch for your own logic, and add a global error handler that checks err instanceof multer.MulterError. The earlier avatar route shows a minimal but realistic handler you can adapt.

Are these three patterns the only good examples of multer file uploads in Node.js: 3 practical examples I should learn?

They’re the patterns that show up most often: single-file image upload, multi-field upload, and temp-staging uploads. Once you understand them, you can combine and tweak them to cover almost every scenario you’ll see in a modern Express app.

Do I still need Multer if I plan to upload directly to S3 or another cloud storage service?

Sometimes yes, sometimes no. If you use presigned URLs and truly go browser → S3, you might skip Multer. But many teams still route uploads through Node.js first for auth checks or processing. In those cases, the temp-staging example of Multer usage is still very relevant.

Explore More Node.js Code Snippets

Discover more examples and insights in this category.

View All Node.js Code Snippets