Search code examples
node.jsmulter

Nodejs - how can I save a file object sent from frontend to the public folder without using middleware like multer?


I have a React client side app and a Nodejs backend.

I am trying to sent a stringified json and some pdf files (uploaded from the client side by react-dropzone) as formdata/multipart in a put request like this:

let formData = new FormData()

formData.append('user', JSON.stringify(jsonData))
formData.append('file_1', user.file_1)
formData.append('file_2', user.file_2)

In my Nodejs backend, I extract the form data with multer:

let multerInstance = multer();
userRouter.put('/user/:user_id', validateJwt, multerInstance.any(), putUser);

and I extract the formdata like this:

const formData = req.body;
const user = JSON.parse(formData.user);
const files = req.files; // here I have file_1 and file_2 as file objects

however, if I use multer to save the files, I have to pass the config settings in the middleware like this:

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './public/uploads');
  },
  filename: function (req, file, cb) {
    var fileFormat = file.originalname.split('.');
    cb(null, file.fieldname + '-' + Date.now() + '.' + fileFormat[fileFormat.length - 1]);
  },
});

let multerInstance = multer({
  storage: storage,
});

let multerInstance = multer();
userRouter.put('/user/:user_id', validateJwt, multerInstance.any(), putUser);

which I don't want to, because I want to decide whether to do the upload action (save to /public/uploads) inside putUser, instead of setting the destination in the multer middleware (which will run before putUser, no matter what)

how can I achieve this?


Solution

  • Glad to figured out the solution using fs.writeFile

    First, use multer on the route like this without any configuration so that it only works as a formdata phaser:

    let multerInstance = multer();
    userRouter.put('/user/:user_id', validateJwt, multerInstance.any(), putUser);
    

    Next, obtain the files with req.files in the route, files that phased by multer will look like this:

    {
        fieldname: 'myField',
        originalname: 'file_1.pdf',
        encoding: '7bit',
        mimetype: 'application/pdf',
        buffer: <Buffer 25 50 44 46 2d 31 2e 33 0a 25 c4 e5 f2 e5 eb a7 f3 a0 d0 c4 c6 0a 34 20 30 20 6f 62 6a 0a 3c 3c 20 2f 4c 65 6e 67 74 68 20 35 20 30 20 52 20 2f 46 69 ... 51728 more bytes>,
        size: 51778
    }
    

    Since I observe that there's a buffer in the file, I can write the file to the target path using the fs module so that the upload action can be done inside the route.

    const files = req.files
    
    const uploadFiles = files.map((file) => {
       return new Promise(function (resolve, reject) {
           let fileFormat = file.originalname.split('.');
           let savePath = file.fieldname + '-' + Date.now() + '.' + fileFormat[fileFormat.length - 1];
           fs.writeFile('./public/uploads/' + savePath, file.buffer, function (err) {
             if (err) {
                return reject();
             }
    
             return resolve();
          });
       });
    });
    
    await Promise.all(uploadFiles);