upload images to separate folders in Nodejs multer

Issue

I have an application to sell cars, I want to upload each car images to a separate folder inside this path country/city.

For example:

Let’s say there is a car in Berlin, Germany. When adding this car I am saving the images to this folder Germany/Berlin.

Now I don’t want to put all the images of the cars in Berlin, Germany inside the same folder, I want to make a separate folder for each car in Berlin, Germany.

My first approach was to count the number of folders inside the base path and then make a new one by adding one to the number.

For example:

Let’s say there are 5 folders inside Germany/Berlin which is the base path for our example

The path would now be like this "Germany/Berlin/6". The problem is that it’s making a different folder for each image.

For example:

Let’s say there are 5 folders inside Germany/Berlin and there are 4 images for the car that is being uploaded.

The first image goes inside Germany/Berlin/6.

The second image goes inside Germany/Berlin/7.

The third image goes inside Germany/Berlin/8.

The fourth image goes inside Germany/Berlin/9.

I think the problem is that the destination function is being executed for each one of the images.

How can I solve this?

Here’s My Code:

const multer = require("multer");
const path = require("path");
const fs = require("fs");
const { onlyImagesAreAllowed } = require("./error_codes");

function uploadFiles(filePath) {
  const storage = multer.diskStorage({
    destination: (req, _file, cb) => {
      const data = req.body;
      let dirsCount = 1;

      let dirPath = `images/${filePath}/${data.country}/${data.city}`;

      const exists = fs.existsSync(dirPath);

      if (!exists) {
        fs.mkdirSync(dirPath, { recursive: true });
      }

      if (exists) {
        dirsCount =
          fs
            .readdirSync(dirPath, { withFileTypes: true })
            .filter((dirent) => dirent.isDirectory())
            .map((dirent) => dirent.name).length + 1;
      }

      dirPath += `${dirsCount}`;

      fs.mkdirSync(dirPath, { recursive: true });

      cb(null, dirPath);
    },
    filename: (_req, file, cb) => {
      const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
      cb(null, file.fieldname + "-" + uniqueSuffix);
    },
  });

  const fileFilter = (req, file, callback) => {
    const acceptedTypes = file.mimetype.split("/");

    if (acceptedTypes[0] === "image") {
      callback(null, true);
    } else {
      callback(null, false);
      callback(new Error(onlyImagesAreAllowed));
    }
  };

  const limits = {
    fileSize: 20 * 1024 * 1024,
  };

  return multer({
    storage: storage,
    fileFilter: fileFilter,
    limits: limits,
  });
}

module.exports = {
  uploadFiles,
};

And the usage is like this:

const express = require("express");
const carsController = require("./cars.controller");
const { uploadFiles } = require("../../utils/upload_files");

const router = express.Router();

router.post(
    "/addCar",
    uploadFiles("cars").array("carGallery", 12),
    carController.addCar
);

module.exports = router;

Solution

If req is the same for each image file (which I assume it is), you can add a custom property to it containing the directory. If that property doesn’t exist, run your logic to determine the next directory name; if it does exist, reuse it.

Something like this:

  destination: (req, _file, cb) => {
  // check if we already determine an upload path for this request
  if (req._upload_path) {
    return cb(null, req._upload_path);
  }

  // no, we didn't, so determine the next upload path
  const data = req.body;
  let dirsCount = 1;
  ... 
  [the rest of your logic]
  ...
  fs.mkdirSync(dirPath, { recursive: true });

  // store the upload path we just determined
  req._upload_path = dirPath;

  cb(null, dirPath);
},

As an aside: if you don’t really need consecutively numbered directories, I would suggest using a unique id (something like a UUID) instead (it’ll be much quicker and less prone to race conditions).

Answered By – robertklep

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published