Search code examples
node.jsexpressfile-uploadmultipartform-datamulter

nodejs : multer - req.file is undefined when using upload.single() in a middleware


hello I am trying to create a custom middle-ware for file upload with multer with customization (allowed file type, destination folder ...). the upload is working but the req.file is always undefined, below you can find my code edit : I have changed the code images to code snippets to make it easier to copy thank you

// route
router.patch('/profile-picture',
    [
        AsyncErrorWrapper(passport.verifyJwt()),
        AsyncErrorWrapper(singleFileUpload('/images/', ['image/png', 'image/jpeg'])),
    ], AsyncErrorWrapper(userController.updateProfilePicture))

// singleFileUpload
const {uploadConfiguration, multerDelay} = require("../services/multer.service")

/**
 * 
 * @param {*} dest destination folder inside the /tmp
 * @param {*} whitelist list of extensions which are allowed to be uploaded
 * @returns 
 */
module.exports = (dest, whitelist, name = 'file') => {
    return async (req, res, next) => {
        // get the upload config obj
        const upload = uploadConfiguration(dest, whitelist)

        // call the upload.single
        upload.single(name)(req, res, next)

        // make sur we wait for the upload to finish to solve the req.file is undefined
        await multerDelay()
        next()
    }
}

// service
const uploadConfiguration = (dest, whitelist) => {
    // storage 
    const storage = multer.diskStorage({
        destination: (req, file, cb) => {
            // set the directory 
            const dir = `${tmpStorage}${dest}`
            mkdirp.sync(dir)
            cb(null, dir)
        },
        filename: (req, file, cb) => {
            cb(null, Date.now() + path.extname(file.originalname));
        },

    })

    // upload filter
    const uploadFilter = (req, file, cb) => {

        if (!whitelist.includes(file.mimetype)) {
            return cb(new AppError('file type is not allowed', 400), false)
        }
        // make this to solve problem with req.file is undefined in the following middleware
        req.file = file
        cb(null, true)
    }

    return multer({
        storage: storage,
        fileFilter: uploadFilter
    })
}

/**
 * 
 * @returns promise that resolve after the queue is clear
 */
const multerDelay = () => {
    console.log('multerDelay');
    return new Promise((resolve) => setTimeout(() => resolve(), 0))
} 
    // controller 
    const updateProfilePicture = async (req, res, next) => {
    
        console.log(req.file) // undefined
        return res.status(200).json({msg: 'hi'})
    
    }


Solution

  • The issue is, you can't run a middleware like that

    When you do

    upload.single(name)(req, res, next)
    

    You are passing next to the function to run it internally

    Running next more than once will always cause errors

    You should split it into two functions

    // singleFileUpload
    module.exports = (dest, whitelist, name = 'file') => {
        return async (req, res, next) => {
            // get the upload config obj
            const upload = uploadConfiguration(dest, whitelist)
    
            // call the upload.single
            upload.single(name)(req, res, next)
        }
    }
    
    // waitForDelay
    module.exports = (dest, whitelist, name = 'file') => {
        return async (req, res, next) => {
            // make sur we wait for the upload to finish to solve the req.file is undefined
            await multerDelay()
            next()
        }
    }
    
    router.patch('/profile-picture',
        [
            AsyncErrorWrapper(passport.verifyJwt()),
            AsyncErrorWrapper(singleFileUpload('/images/', ['image/png', 'image/jpeg'])),
            AsyncErrorWrapper(waitForDelay()),
        ], AsyncErrorWrapper(userController.updateProfilePicture))
    

    ^ untested code