Back to Blog
Node.js13 min read

Multer File Upload in Express.js: Complete Guide

Multer is a Node.js middleware for handling multipart/form-data, primarily used for file uploads in Express.js applications. In this guide, we'll learn how to implement file uploads for product images and galleries in an inventory management system.

Multer is a Node.js middleware for handling multipart/form-data, primarily used for file uploads in Express.js applications. In this guide, we'll learn how to implement file uploads for product images and galleries in an inventory management system.

Installation

npm install multer

Multer Configuration

Setting up Multer with disk storage:

const multer = require("multer");
const path = require("path");
const fs = require("fs");

// Create uploads directory if it doesn't exist
const uploadDir = "uploads/";
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, uploadDir);
  },
  filename: function (req, file, cb) {
    const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 10000);
    const uploadedFileName =
      file.fieldname + "-" + uniqueSuffix + "." + file.mimetype.split("/")[1];
    cb(null, uploadedFileName);
  },
});

const upload = multer({ 
  storage: storage,
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB limit
  },
  fileFilter: (req, file, cb) => {
    // Only allow images
    if (file.mimetype.startsWith("image/")) {
      cb(null, true);
    } else {
      cb(new Error("Only image files are allowed!"), false);
    }
  },
});

Single File Upload

const uploadSingleFile = upload.single("file");

function storeSingleFile(req, res, next) {
  return new Promise((resolve, reject) => {
    uploadSingleFile(req, res, async function (err) {
      if (err instanceof multer.MulterError) {
        console.log("Multer error:", err);
        reject(err);
      } else if (err) {
        console.log("Upload error:", err);
        reject(err);
      }
      resolve({
        message: "File uploaded successfully",
        body: req.body,
        file: req.file ? req.file : null,
      });
    });
  });
}

// Usage in route
router.post("/upload", async (req, res, next) => {
  try {
    await storeSingleFile(req, res, next);
    res.json({
      success: true,
      message: "File uploaded",
      file: req.file,
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      message: error.message,
    });
  }
});

Multiple File Upload

Handling product images with main image and gallery:

// For products: main image (required) + gallery images (optional)
const uploadProductFiles = upload.fields([
  { name: "product_image", maxCount: 1 }, // Main product image (required)
  { name: "product_gallery", maxCount: 10 }, // Gallery images (optional, max 10)
]);

function storeProductFiles(req, res, next) {
  return new Promise((resolve, reject) => {
    uploadProductFiles(req, res, async function (err) {
      if (err instanceof multer.MulterError) {
        console.log("Multer error for product files:", err);
        reject(err);
      } else if (err) {
        console.log("Upload error for product files:", err);
        reject(err);
      }
      resolve({
        message: "Files uploaded successfully",
        body: req.body,
        files: req.files ? req.files : null,
      });
    });
  });
}

// Usage in route
router.post("/products", async (req, res, next) => {
  try {
    await storeProductFiles(req, res, next);
    
    // Access files
    const mainImage = req.files?.product_image?.[0];
    const galleryImages = req.files?.product_gallery || [];
    
    // Process files...
    res.json({
      success: true,
      message: "Product created",
      files: {
        mainImage: mainImage?.filename,
        gallery: galleryImages.map(f => f.filename),
      },
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      message: error.message,
    });
  }
});

File Validation

Adding custom validation:

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB
    files: 10, // Max 10 files
  },
  fileFilter: (req, file, cb) => {
    // Check file type
    const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error("Invalid file type. Only JPEG, PNG, and WebP are allowed!"), false);
    }
  },
});

Error Handling

router.post("/upload", async (req, res) => {
  try {
    await storeProductFiles(req, res);
    
    // Check if main image is required
    if (!req.files?.product_image || req.files.product_image.length === 0) {
      return res.status(400).json({
        success: false,
        message: "Product image is required",
      });
    }
    
    // Process files...
  } catch (error) {
    if (error instanceof multer.MulterError) {
      if (error.code === "LIMIT_FILE_SIZE") {
        return res.status(400).json({
          success: false,
          message: "File size too large. Maximum size is 5MB",
        });
      }
      if (error.code === "LIMIT_FILE_COUNT") {
        return res.status(400).json({
          success: false,
          message: "Too many files. Maximum is 10 files",
        });
      }
    }
    
    return res.status(400).json({
      success: false,
      message: error.message || "File upload failed",
    });
  }
});

Best Practices

  • Always validate file types and sizes
  • Use unique filenames to prevent conflicts
  • Set appropriate file size limits
  • Handle errors gracefully
  • Create uploads directory if it doesn't exist
  • Clean up old files periodically
  • Consider using cloud storage for production

Conclusion

Multer provides a straightforward solution for handling file uploads in Express.js applications. With proper configuration, validation, and error handling, you can create robust file upload functionality for product images, galleries, and other file types in inventory management systems.