Search code examples
node.jsvue.jsexpressmulter

Multer is unable to save uploaded file through FormData


I've had trouble saving an uploaded file sent through a VueJS frontend to a Node + Express backend. Data is sent from the frontend through multipart form data and as such all the metadata and the file itself is appended within the request body. The following is the router on the backend

import {submitFlag} from '../some/dir/controller.js'

const router = express.Router();
const app = express();
app.use(express.json());


router.post('/submit-flag', [], submitFlag)

and the backend controller that handles the API request from the router

import multer from 'multer';
const evidencePathSave = '../uploads/flagEvidence'

export const submitFlag = async (req, res) => {
    const evidenceStorage = multer.diskStorage({
        destination: function (req, file, cb) {
            cb(null, evidencePathSave)
        },
        filename: function (req, file, cb) {
            cb(null, req.body.flagEvidence.name);
        }
    })
    const uploadEvidence = multer({
        storage: evidenceStorage,
        fileFilter: (req, res, cb) => {
            const filetypes = /pdf|doc|docx|txt/;
            const mimetype = filetypes.test(req.body.flagEvidence.type);
            const extname = filetypes.test(path.extname(req.body.flagEvidence.name));
            // if mimetype && extname are true, then no error
            if(mimetype && extname){
                return cb(null, true);
            }
            // if mimetype or extname false, give an error of compatibilty
            return cb("The uploaded file, isn't compatible");
        }
    }).fields([
        {name: 'flagEvidence', maxCount: 1}
    ]);
    uploadEvidence(req, res, function(err) {
        if (err) {
            console.log(err)
            res.status(500).send("Error in Saving");
        }
    })
    res.status(200)
}

The function does not return any error nor status 500 message however when checking the local storage, the file does not seem to be found anywhere. Removing the file filter function does not seem to work.

The frontend Vue function is as follow

submitFlag(event) {
    this.flagEvidence = event.target.files[0]
    var formData = new FormData()
    formData.append('flagSubject', this.flagSubject)
    formData.append('flagWriteup', this.flagWriteup)
    formData.append('flagEvidence', this.flagEvidence)
    formData.append('userID', this.user._id)
    formData.append('username', this.user.username)
    formData.append('publicationID', this.publicationID)
    formData.append('filename', this.flagEvidence.name)
    axios({
        method: "POST",
        url: serverSide.submitFlag,
        data: formData,
        headers: {"Content-type": "multipart/form-data"}
    })
    .then((res) => {
      this.$router.push({name: "news", query:{id: this.publicationID}})
    })
    .catch(function (error) {
        console.error(error.response);
    });
}

Solution

  • Your usage of multer is incorrect. multer.fields() returns a function that should be passed as middleware to your route. Here is how that would look like in your case:

    const evidenceStorage = multer.diskStorage({
      destination: function (req, file, cb) { ... },
      filename: function (req, file, cb) {
        cb(null, file.originalname);
      },
    });
    
    const uploadEvidence = multer({
      storage: evidenceStorage,
      fileFilter: (req, res, cb) => { ... },
    );
    
    router.post(
      '/submit-flag',
      uploadEvidence.fields([{ name: 'flagEvidence', maxCount: 1 }]),
      submitFlag
    );
    

    Also instead of req.body.flagEvidence.name I'd use file.originalname when configuring DiskStorage. Have a look at this free Request Parsing in Node.js Guide when working with file uploads in Node.js.