Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multer: dynamic destination path

Tags:

node.js

multer

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!

like image 880
Zeokav Avatar asked Jan 02 '17 15:01

Zeokav


3 Answers

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({});
    });

});
like image 57
Łukasz Avatar answered Oct 06 '22 22:10

Łukasz


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]);
}

`

like image 2
Clauzzz Avatar answered Oct 07 '22 00:10

Clauzzz


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}`)
    }
  })
})
like image 2
ozgrozer Avatar answered Oct 06 '22 22:10

ozgrozer