13

I am writing a node application and I was looking for something to upload files on the server. I could get files to upload when there was only one static directory. But I need to make directories per user and then upload files to those, according to the user that's logged in. I looked stuff up but everything that I try ends in an Error: ENOENT: no such file or directory, open ... error. What I am trying to do currently is this -

let storage = multer.diskStorage({
  destination: function(req, file, cb) {
    let dest = path.join(__dirname, './documents', 'somenameigetfromtheuser');
    let stat = null;
    try {
      stat = fs.statSync(dest);
    }
    catch (err) {
      fs.mkdirSync(dest);
    }
    if (stat && !stat.isDirectory()) {
      throw new Error('Directory cannot be created');
    } 
    cb(null, dest);
  }
});

let upload = multer({
  storage: storage,
  dest: 'documents/'
});

app.post('/testUpload', upload.single('testfile'), (req, res) => {
  res.json({
    test: 'test'
  })
});

There is a similar question that has been answered but it doesn't work that way for me because I want the directory name from the request object.

When I remove the storage property in my multer initialization, the files are stored in the documents directory with a random name. I want the file to have its original name and I want it to be stored in a directory where I get the name of the directory from the req object.
Help a brother out, thanks!

Community
  • 1
  • 1
Zeokav
  • 1,653
  • 4
  • 14
  • 29

6 Answers6

6

edited

See https://github.com/expressjs/multer#diskstorage

Note that req.body might not have been fully populated yet. It depends on the order that the client transmits fields and files to the server.

Due to that, first write file in temp directory, read directory name from req and move file:

fs = require('fs-extra'); //npm install fs.extra
...

var storage = multer.diskStorage({ 
    destination: function (req, file, cb) {
        cb(null, '../tempDir/')
    },
    filename: function (req, file, cb) {
        cb(null, file.originalname)
    }
});

var upload = multer({ 
    storage: storage
}).single('file');

upload(req, res, function (err) {
    if (err) {
        res.json({});
        return;
    }

    var dir = JSON.parse(req.body.data).directory;
    var filename = req.file.filename;

    fs.move('../tempDir/' + fileName, '../tempDir/' + dir + '/' + fileName, function (err) {
        if (err) {
            return console.error(err);
        }

        res.json({});
    });

});
Łukasz
  • 2,131
  • 1
  • 13
  • 28
6

Here's code for dynamic path by argument!

exports.upload = (folderName) => {
  return imageUpload = multer({
    storage: multer.diskStorage({
      destination: function (req, file, cb) {
        const path = `src/assets/uploads/${folderName}/`;
        fs.mkdirSync(path, { recursive: true })
        cb(null, path);
      },

      // By default, multer removes file extensions so let's add them back
      filename: function (req, file, cb) {
        cb(null, Date.now() + path.extname(file.originalname));
      }
    }),
    limits: { fileSize: 10000000 },
    fileFilter: function (req, file, cb) {
      if (!file.originalname.match(/\.(jpg|JPG|webp|jpeg|JPEG|png|PNG|gif|GIF|jfif|JFIF)$/)) {
        req.fileValidationError = 'Only image files are allowed!';
        return cb(null, false);
      }
      cb(null, true);
    }
  })
}

and call it enter image description here

3

Here's what I do for uploading files to dynamic directories.

In frontend I use URL parameters to pass user IDs.

await axios({
  method: 'post',
  data: formData,
  url: '/api/upload?userId=123',
  headers: { 'content-type': 'multipart/form-data' }
})

In backend get that parameter and use for destination. Also create the directory if it doesn't exist.

const upload = multer({
  storage: multer.diskStorage({
    destination: (req, file, cb) => {
      const directory = `./public/uploads/${req.query.userId}`

      if (!fs.existsSync(directory)) {
        fs.mkdirSync(directory, { recursive: true })
      }

      cb(null, directory)
    },
    filename: (req, file, cb) => {
      cb(null, `${Date.now()}-${file.originalname}`)
    }
  })
})
ozgrozer
  • 1,824
  • 1
  • 23
  • 35
  • Use query param in the only choice because muleter middleware is inserted before the body parser. That's why **req.body.foo** is undefined. Thanks!! – JRichardsz Aug 29 '22 at 03:49
  • Simple and best. Especially the creating the directory part as if we use a custom callback, multer will throw an error saying ENOENT No such directory. – MrSrv7 Sep 29 '22 at 12:47
2

Make sure you append first the textfields on the client-side and only then do you append the files. In my case i had something like this:

` 

for(let i=0; i<files.length;i++)
{
  formData.append("files[]",files[i]);
}
formData.append("username",username);

  `

The fix was to first append the textfield like so:

`

formData.append("username",username);
for(let i=0; i<files.length;i++)
{
  formData.append("files[]",files[i]);
}

`

Clauzzz
  • 129
  • 5
0

In my project I use multer as follow:

1.Store the file first in a common directory, like /tmp/.

2.Copy/move the file anywhere you want, to CDN in my case, and to a user folder in yours.

3.Remove the original file in /tmp if needed.

And maybe let upload = multer({ storage: storage, dest: 'documents/' }); you should remove the dest here since you specified dest in storage, right?

YLS
  • 697
  • 5
  • 9
  • And how do you move the file? I tried using rename for this, but that doesn't work at all for me. – Zeokav Jan 02 '17 at 16:15
  • Well, here's another question here about writing file to another in node.js. http://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js – YLS Jan 02 '17 at 16:25
  • What a brilliant ideea! This solves 2 things. 1. temp to clean it afterwards, 2. with a second call you can do anything (because you have the unique filename from the upload response). Thank you – Sorin Veștemean Jan 19 '21 at 20:40
  • What if two user added the same file name at same time @YLS – Umar Farooque Khan Feb 12 '22 at 21:23
0
const storage = multer.diskStorage({
    destination: function(req, file, callback){
        callback(null, path.dirname('D:/') + 'Integra Qamba Site/');
    },
    filename: function(req, file, callback){
        let data = new Date();
        callback(null, dateTime +".jpg");
    }
});
  • add comments(documentation). – Vishnu Oct 11 '21 at 12:34
  • This doesnt say how to change it dynamically. The idea is that the name would come inside the request, there is no other way. The comment in the question links to the right answer – lesolorzanov Nov 02 '21 at 15:45