0

Good Day all,

I am trying to upload a multi-part formdata using multer. The file uploads to the correct directory but then program execution stops. Im testing the route with postman. The middleware module is a self executing function that I pass to my route however after the file saves the console just displays "undefined" and postman throws Could not get response

After the file upload is complete it is supposed to move to the "updateRules" middleware to validate the form.

The funny thing is the "console.log(before return)" in my "fileUpload.js" that executes immediatly after I change & save the file.

I dont know if its the self invoking function that does this but im new to Node and Im just following a tutorial. The auth & updateRules middlewares work fine but program execution hangs after the file uploads to the specified path. Also the user.id is a digit like 6 for example

Below is my route

const router = require('express').Router()

const { validate } = require('../validators')
const { update } = require('../controllers/userController')
const { auth } = require('../middleware/auth')
const { rules : updateRules } = require('../validators/user/update')
const { userFile }  = require('../middleware/fileUpload')

router.post('/update', [auth, userFile, updateRules, validate], update)

module.exports = router

Here is my fileUpload.js middleware

const multer = require('multer')
const fs = require('fs')
const path = require('path')

exports.userFile = ((req,res, next) => {

  const getFileType = file => {
    const mimeType = file.mimetype.split('/')
    return mimeType[mimeType.length - 1]
  }

  const generateFilename = (req, file, cb) => {
    console.log(file.originalName)
    const extension = getFileType(file)

    const filename = Date.now() + '-' + Math.round(Math.random() * 1E9 ) + '.' + extension
    cb(null,file.fieldname + '-' + filename)
  }

  const fileFilter = (req, file, cb) => {

    const extension = getFileType(file)

    const allowedType = /jpeg|jpg|png/

    const passed = allowedType.test(extension)

    if(passed){
      return cb(null, true)
    }

    return cb(null, false)
  }

  const storage = multer.diskStorage({
 
    destination: function(req, file, cb) {
  
      const { id } = req.user
      const dest = `./uploads/user/${id}` 

      fs.access(dest, (error) => {
    
        // if folder doesnt exist
        if(error) {
          console.log("folder doesnt exist")
          return fs.mkdir(dest, {recursive: true} ,(error) => {            
            cb(error, dest)
          })
        } else {
          console.log("folder exists")
          fs.readdir(dest, (error, files) => {
            if(error) throw error

            for(const file of files) {
              fs.unlink(path.join(dest, file), error => { if(error) throw error } )
            }
          })

          return cb(null, dest)
        } 
      })
    },

    filename: generateFilename
  })
  console.log("before return")
  return  multer({storage, fileFilter}).single('avatar')
})()

I also tried exporting storage & fileFilter functions, call multer from router but the rsult is the same

route.js

const multer = require('multer')
const { storage, fileFilter }  = require('../middleware/fileUpload')

router.post('/update', 
[auth, multer({storage, fileFilter}).single('avatar'), 
updateRules, validate], update)

3 Answers 3

1

In router you need to like its done in this example.

router.put('/',multer({
    storage: storage,
    limits: {
        fileSize: 1000 * 1 * 3000
    }
}).single('productPic') //For single image and for multiple you can user array
Sign up to request clarification or add additional context in comments.

4 Comments

But how is that going to affect my fileUpload.js in terms of what to export and how to import inside of route ? And also should it be a self invoking function or not ?
you need to export your storage function where you implemented it and import where you route is defined.
I tried but had the same result, please see the edit i made to question. Thanks for helping out by the way
In you storage function you are using return multer({storage, fileFilter}).single('avatar') after console.log(return before) remove that line and then try it.
1

It seems to me using IIFE in fileUpload.js is the reason why it's breaking, you see when multer is saving the files to disk its an async operation as it takes a couple of micro-seconds for multer to actually save the file and then call next() since you are using IIFE your call stack doesn't wait for multer operation and just moves on..

Secondly, this approach of yours is not an ideal one / at least not a recommended one, maybe your project needs it, however here is how I could do the multer configuration in my project

const multer = require("multer");

/* Custom Multer Filter */
const multerFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image')) {
    cb(null, true);
  } else {
    cb(new Error('Not an image! Please upload only images.'), false);
  }
};

/* Multer storage */
const multerStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'public/img/');
  },
  filename: (req, file, cb) => {
    const ext = file.mimetype.split('/')[1];
    cb(null, `My-Custom-File-Prefixer.${ext}`);
  }
});

/* Endpoint Object For Single File */
const upload = multer({
  storage: multerStorage,
  fileFilter: multerFilter
});

/* Endpoint Object For Array of Files */
const MultiFileUpload = multer({
  storage: multerStorage,
  limits: { fileSize: 2000000 }, // In bytes: 2000000 bytes = 2 MB
  fileFilter: ImageFilter,
});




//Use 'uploadSingle' if there is just one File
exports.uploadSingle = upload.single('photo');
/* replace 'Photo' with the filed containing the single image */

/*Use 'arrayOfFile' if there are multiple fields containing files and set the limit of each field */

exports.arrayOfFile = MultiFileUpload.fields([
  { name: "albumCover", maxCount: 1 },
  { name: "albumImages", maxCount: 5 },
]);

And then on my router, I would use uploadSingle if my request is having a single file or arrayOfFile if my request has multiple files on multiple fields

4 Comments

I tried your way but now its not even reaching the fileUpload.js
How do you import & call it in your router ?
Soory for taking solongto respond im in Africa :)
What i shared was a Generalized code and not a working one, as you have to modify it as per your project requirements. For using it on the router router.post('/update', uploadSingle, nextmiddleware) For handling single file OR replace the uploadSingle with arrayOfFile for handling multiple files
1

I managed to find an answew guys. It turns out my updateRules should have been called with brackets.

Thanks for you help and input

And I decided to follow your pattern, Nishant S Vispute

It is way more clean the the self-invoking function

 router.post('/update', [auth, uploadSingle, updateRules(), validate ], update)

1 Comment

If you are following the pattern then you can even outsource the whole Multer part into a separate file and call it to say MulterMIddleware.js and then use that file directly on your router or any other middleware where you have file operations.. this way you can make all your middleware use a common code base for handling file operations

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.