skaz
skaz

Reputation: 301

How can I return Multer Error to Client while using Express-Validator?

Updated Post: Scroll to bottom of post for updated info


Original Post

I need some help on this one. I'm creating a route that takes FormData, validates the file data via Multer (images in this case), then validates the string data using Express-Validator. I've created a working route that completes both validations, but I can't figure out how to take any errors from Multer and return them to the client.

I have set Multer before Express-Validator, so that the req.body can be read by Express-Validator. With that, I can't figure out how to (or if I'm able at all) to pass in the Multer errors for sending back in the response.

My example below should include everything needed for examination, but if you need additional information, please let me know.

const multer = require('multer')
const {
    check,
    validationResult
} = require('express-validator/check');
const {
    sanitizeBody
} = require('express-validator/filter');


const imageUpload = multer({
    dest: 'uploads/',
    limits: {
        fileSize: 1000000
    },
    fileFilter: function (req, file, cb) {
        let filetypes = /jpeg|jpg/;
        let mimetype = filetypes.test(file.mimetype);
        let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
        if (mimetype && extname) {
            return cb(null, true);
        }
        cb(new Error('Invalid IMAGE Type'))
    }
}).fields([{
        name: 'cover_image',
        maxCount: 1
    },
    {
        name: 'more_images',
        maxCount: 2
    }
])


const validationChecks = [
    check('street', 'Invalid Street Name').matches(/^[a-z0-9 ]+$/i).isLength({
        min: 1,
        max: 25
    }).trim().escape(),
    check('city', 'Invalid City Name').matches(/^[a-z ]+$/i).isLength({
        min: 1,
        max: 15
    }).trim().escape()
]


router.post('/addnewproperty', imageUpload, validationChecks,(req, res, next) => {  
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        console.log('text validation FAILED');
        return res.status(400).json({
            errors: errors.array()
        });
    }
    console.log('validation PASSED');
})

Update 2/6/19

Okay, I think I have found a solution, although not what I expected.

By using the next() function within express, I'm able to utilize Multer in the first route handler where I can receive and send back Multer errors in the response. If no errors arise in this first route handler, I can call next(), to then move along to the next route handler for utilizing express-validator where I can check for and send any errors that arise from string validation.

The code below is a working example of what I'm describing. Not sure if this is acceptable code, but it is working upon some light testing. Any opinions or recommendations on this are welcome in the comments below.


// Here's the meat of what I changed.  
// The config and variables set in the previous code are the same. 

router.post('/addnewproperty',(req, res, next) => {
    imageUpload(req,res,(err)=>{
        if(err){
            console.log(err.message);
            return res.status(400).json(err.message)
        }
        next()
    })
})

router.post('/addnewproperty',validationChecks,(req,res)=>{
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({
            errors: errors.array()
        });
    }
    return res.sendStatus(200)
})

I'll leave this question open in case someone has a better solution of obtaining what I originally set out to do besides the code above.

Upvotes: 5

Views: 5772

Answers (5)

AdHorger
AdHorger

Reputation: 526

I faced a very similar issue, and just wanted to give my 2 cents. The OP's solution is right for the most part, and I haven't tested it, but it might cause unexpected behavior.

You still have to manually call the multer middleware, but instead of defining the same route twice, a more elegant solution is grouping all the middleware in one route definition. So, using the OP's example, you can do it like this:

router.post('/addnewproperty',
  (req, res, next) => {
    imageUpload(req, res, (err) => {
      if (err) {
        console.log(err.message);
        return res.status(400).json(err.message);
      }
      next();
    });
  },
  validationChecks,
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        errors: errors.array()
      });
    }
    return res.sendStatus(200);
  }
);

Also, as one of the comments pointed out, the multer middleware has to be called first, otherwise req.body will be undefined.

Upvotes: 0

Todd Hammer
Todd Hammer

Reputation: 311

I ran into this problem today except I am not using a router. I wanted to be able to return a JSON response of the error when the fileType validation failed. I thought I would share my solution as it may help somebody else. My solution was to add a fourth parameter to the app.post() function call like so:

app.post("/post", fileUpload.single('file'), function(req, res) { 
    //do some stuff after a successful upload.
}, 
function(err, req, res, next) {
    //File upload encountered an error as returned by multer
    res.status(400).json({error: err.message});
})

Upvotes: 0

Javier Gutiérrez
Javier Gutiérrez

Reputation: 51

I have been using a function that creates a Multer middleware that can be placed anywhere in the middleware chain. After it, you can use req.body without the other binary fields.

import { Router } from 'express';
import multer from 'multer';

function makeMulterUploadMiddleware(multerUploadFunction) {
    return (req, res, next) =>
        multerUploadFunction(req, res, err => {
            // handle Multer error
            if (err && err.name && err.name === 'MulterError') {
                return res.status(500).send({
                    error: err.name,
                    message: `File upload error: ${err.message}`,
                });
            }
            // handle other errors
            if (err) {
                return res.status(500).send({
                    error: 'FILE UPLOAD ERROR',
                    message: `Something wrong ocurred when trying to upload the file`,
                });
            }

            next();
        });
}

const upload = multer({ dest: 'uploads/' });

const multerUploadMiddleware = makeMulterUploadMiddleware(upload.single('image'));

const someRouter = Router();

someRouter.post('', multerUploadMiddleware, (req, res) => {
    // now body contains all the fields except the one with the file
    res.send(req.body);
});

export { someRouter };

I'm handling the req.body with the @hapi/joi npm package, but this should work with other validators.

note: The reason I'm not using err instanceof multer.MulterError to check if its a Multer error as described in the Multer docs (https://github.com/expressjs/multer) is because of some typescript type-checking errors.

Upvotes: 5

akaMahesh
akaMahesh

Reputation: 381

Hey i will leave you with some snippets which worked for me ,

  • Multer without using file

    const express = require('express');
    const multer = require('multer');
    const router = express.Router();
    const { check, validationResult } = require('express-validator');
    
    var upload = multer();
    
    var tagCreateValidator = [
      check('name', 'Name is required')
        .not()
        .isEmpty()
    ];
    
    // @route   POST api/tags
    // @desc    Create Tag
    // @access  Public
    router.post('/', upload.any(), tagCreateValidator, (req, res, next) => {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
      }
    
      res.send(req.body);
    });
    
  • this one is with file although i am not using express validator right now, i will update this answer once this is done

    const express = require('express');
    const Multer = require('multer');
    const gcsMiddlewares = require('../../gcs_middleware');
    const router = express.Router();
    
    const multer = Multer({
      storage: Multer.storage,
      limits: {
        fileSize: 10 * 1024 * 1024 //Maximum file size is 10MB
      }
    });
    
    // @route   POST api/users
    // @desc    Register user
    // @access  Public
    router.post(
      '/',
      multer.single('avatar'),
      gcsMiddlewares.sendUploadToGCS,
      (req, res, next) => {
        var imageUrl;
        if (req.file && req.file.gcsUrl) {
          imageUrl = req.file.gcsUrl;
        }
        var responseBody = {
          file: imageUrl,
          body: req.body
        };
    
        res.send(req);
      }
    );
    
    module.exports = router;
    

Upvotes: 0

Azami
Azami

Reputation: 2161

You can get the error by calling the imageUpload middleware directly instead of using it in a middleware chain like in your code.

Non-tested snippet, but hopefully will at least nudge you in the right direction:

router.post('/addnewproperty', validationChecks, (req, res, next) => {  
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
        console.log('text validation FAILED');
        return res.status(400).json({
            errors: errors.array()
        });
    }

    imageUpload(req, res, (multerErr) => {
        if(multerErr){
            console.log('Multer validation FAILED');
            return res.status(400).json({
                errors: [multerErr.message]
            });
        }else{
            console.log('validation PASSED');
        }  
    });
})

For more on the subject, here are the official Multer docs on Error handling.

Upvotes: 0

Related Questions