100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Node.js & Express Backend
30 minintermediate

Multer — Single and Multi-File Uploads

Multer — Single and Multi-File Uploads

File uploads are a common requirement: profile photos, product images, document attachments, CSV imports. In Express, the standard package for handling multipart/form-data (the encoding used for file uploads) is Multer. Multer parses the incoming request, extracts file buffers or writes them to disk, and populates req.file (single upload) or req.files (multiple uploads) so your route handler can process them.

Analogy🏏Cricket
Think of it like cricket: When a team submits equipment for a match, the equipment manager doesn't mix everything in one bag. Each item goes through an intake process: the bat is checked for dimensions, the helmet for safety standards, the ball for condition. Multer is the equipment intake manager — it intercepts every file in the upload, checks its type and size, stores it in the right location, and rejects anything that doesn't meet the rules. A 50 MB video file trying to get in as 'profile_photo.jpg' gets stopped at the gate, just like an overweight bat.

Basic Setup and Storage Engines

Multer supports two storage engines: diskStorage (saves files to the local filesystem) and memoryStorage (stores files as Buffer objects in memory). Use memoryStorage when you intend to process files immediately and upload them to cloud storage (S3, Cloudinary) without touching disk. Use diskStorage for large files or when the file path needs to be stored in the database.

bash
npm install multer
javascript
const multer = require('multer');
const path   = require('path');
const crypto = require('crypto');

// Disk storage configuration
const diskStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, './uploads');  // folder must exist
  },
  filename: (req, file, cb) => {
    // Randomise filename to prevent path traversal / overwrites
    const ext      = path.extname(file.originalname);
    const basename = crypto.randomBytes(16).toString('hex');
    cb(null, basename + ext);
  }
});

// Memory storage (for cloud upload workflow)
const memStorage = multer.memoryStorage();

const upload = multer({
  storage: diskStorage,
  limits: {
    fileSize: 5 * 1024 * 1024  // 5 MB max
  },
  fileFilter: (req, file, cb) => {
    const allowed = ['image/jpeg', 'image/png', 'image/webp'];
    if (allowed.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Only JPEG, PNG, and WebP images are allowed'), false);
    }
  }
});
Analogy🏏Cricket
Think of it like cricket: diskStorage is like the BCCI's physical trophy room — trophies are stored on numbered shelves (randomised filenames) so you can always retrieve them. memoryStorage is like a player holding the trophy just long enough to have a photo taken before it goes to a display case in another city (uploaded directly to cloud storage without saving locally). Different workflows for different purposes.

Single File Upload

upload.single('fieldName') returns middleware that processes one file from the multipart request field named fieldName. After the middleware runs, the file is available as req.file with properties: fieldname, originalname, mimetype, size, path (disk) or buffer (memory).

javascript
// Single file upload: profile photo
router.post(
  '/users/avatar',
  authenticate,
  upload.single('avatar'),
  async (req, res) => {
    if (!req.file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }

    // req.file = {
    //   fieldname: 'avatar',
    //   originalname: 'photo.jpg',
    //   mimetype: 'image/jpeg',
    //   size: 204800,
    //   path: 'uploads/abc123def456.jpg',  // (disk storage)
    //   filename: 'abc123def456.jpg'
    // }

    await User.updateOne(
      { _id: req.user.sub },
      { avatar: '/uploads/' + req.file.filename }
    );

    res.json({ avatar: '/uploads/' + req.file.filename });
  }
);
Analogy🏏Cricket
Think of it like cricket: upload.single('avatar') is like the gate steward who allows exactly one bat per player. If you show up with two bats (multiple files), he takes only the first one and says 'just one, please.' If you show up without any bat, the route handler catches it with req.file check.
Lesson 25 of 36
0% complete