Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

10. How to Upload Files in Backend — Multer

Lesson 10 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

What is Multer?

Multer is an Express middleware for handling multipart/form-data requests — the encoding type used when submitting forms that include file uploads. Without Multer, req.body will be empty and req.file will be undefined when a file is sent.

npm install multer

---

diskStorage vs memoryStorage

AspectdiskStoragememoryStorage
Storage locationLocal file systemNode.js memory (RAM) as Buffer
SpeedSlower (disk I/O)Faster (in-memory)
File size limitLimited by disk spaceLimited by available RAM
File pathreq.file.path
File bufferreq.file.buffer
Use caseBefore cloud upload (temp storage)Small files, direct processing
Temp cleanupMust delete manuallyAutomatic on GC
Use diskStorage when uploading to Cloudinary — save to a temp folder, upload to cloud, then delete the local file. Use memoryStorage only for small files that are processed in-memory (e.g., reading a CSV).

---

Multer Configuration with diskStorage

import multer from 'multer';
import path from 'path';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, './public/temp');  // Temp folder on server
  },
  filename: (req, file, cb) => {
    // Use Date.now() + random number to ensure unique filenames
    const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
    const ext = path.extname(file.originalname);
    cb(null, `${file.fieldname}-${uniqueSuffix}${ext}`);
  },
});

---

File Filter

Reject non-image files before they reach the server:

const fileFilter = (req, file, cb) => {
  const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
  if (allowedMimeTypes.includes(file.mimetype)) {
    cb(null, true);   // Accept file
  } else {
    cb(new Error('Only JPEG, PNG, and WebP images are allowed'), false); // Reject
  }
};

---

Upload Options: single, array, fields

const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 } });
// limits.fileSize: 5MB maximum

// Single file — req.file
router.post('/avatar', upload.single('avatar'), controller);

// Multiple files, same field — req.files (array)
router.post('/gallery', upload.array('images', 5), controller);  // max 5

// Multiple fields with different names — req.files (object)
router.post('/register', upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'coverImage', maxCount: 1 },
]), controller);
// Access: req.files?.avatar?.[0], req.files?.coverImage?.[0]

---

Cloudinary Integration

After Multer saves the file to a temp folder, upload it to Cloudinary, get the secure URL, then delete the local temp file:

// src/config/cloudinary.js
import { v2 as cloudinary } from 'cloudinary';
import fs from 'fs';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export const uploadOnCloudinary = async (localFilePath) => {
  if (!localFilePath) return null;
  try {
    const response = await cloudinary.uploader.upload(localFilePath, {
      resource_type: 'auto',  // auto-detect image/video/raw
      folder: 'myapp',        // Cloudinary folder
    });
    // File uploaded successfully — delete local temp file
    fs.unlinkSync(localFilePath);
    return response;  // response.secure_url, response.public_id
  } catch (error) {
    // Upload failed — still delete local temp file
    if (fs.existsSync(localFilePath)) {
      fs.unlinkSync(localFilePath);
    }
    return null;
  }
};

---

Full Controller Flow with File Upload

  1. Multer middleware reads the multipart/form-data request
  2. Validates file type and size
  3. Saves to public/temp/
  4. Controller reads req.files?.avatar[0]?.path
  5. Calls uploadOnCloudinary(localPath)
  6. Receives { secure_url, public_id } from Cloudinary
  7. Stores the secure_url in MongoDB
  8. Local temp file is deleted (by the upload utility)

---

Handling Multer Errors

// In your error handler middleware:
if (err instanceof multer.MulterError) {
  if (err.code === 'LIMIT_FILE_SIZE') {
    return res.status(400).json({ message: 'File too large. Max 5MB allowed.' });
  }
  if (err.code === 'LIMIT_FILE_COUNT') {
    return res.status(400).json({ message: 'Too many files uploaded.' });
  }
  if (err.code === 'LIMIT_UNEXPECTED_FILE') {
    return res.status(400).json({ message: `Unexpected field: ${err.field}` });
  }
}

---

Deleting Old File from Cloudinary on Update

When a user updates their avatar, delete the old image from Cloudinary to avoid orphaned files and storage costs:

export const deleteFromCloudinary = async (publicId) => {
  if (!publicId) return null;
  return await cloudinary.uploader.destroy(publicId);
};

Store the public_id alongside the URL in your MongoDB document so you can delete it later.