Home > Enterprise >  Formidable: Parse multipart/form-data request THEN upload file
Formidable: Parse multipart/form-data request THEN upload file

Time:06-01

I have a problem since yesterday with my node express app.

The thing what I want to do is the next one:

Middleware 1, step 1 (parse request): Parse the multipart/form-data in req.body with formidable and do what I want to do with the data (such validation), then go to step 2 if all being good.

Middleware 2, step 2 (upload file): Upload file with formidable (already being parsed)

Problems:

  • Multer: Already tested with Multer but impossible, I have to put the middleware 2 before the middleware 1 otherwise the req.body is empty (also tested multer.none()).
  • Formidable: Actively testing with formidable but when I parse the request in the Middleware 1, formidable won't do anything in the Middleware 2 (because my req.body is already parsed and he didn't find the file ?)

I know there is a filter function but I really want to have two seperated middlewares.

Express middlewares

const formidable = require('formidable');

// parse request
module.exports.middleware1 = async (req, res, next) => {
  const form = formidable({ multiples: true });

  form.parse(req, (err, fields, files) => {
    if (err) {
      next(err);
      return;
    }
    
    // request parsed
    // do what I want to do with the fields (such validation)
    console.log(fields)
    
    next()
  });
}

// upload file
module.exports.middleware2 = async (req, res, next) => {
  const options = {
    uploadDir: 'test',
    keepExtensions: true,
    maxFiles: 1,
    filename: function (name, ext, part, form) {
      return name
    },
    filter: function ({name, originalFilename, mimetype}) {
      return true
    }
  }
  const form = formidable(options);
  form.parse(req, (err, fields, files) => {
    if (err) {
      next(err);
      return;
    }
    
    // file uploaded, go to controller
    
    next()
  });
}

Front app

publishPost: function () {
  let formData = new FormData()

  const post = {
    filters: {
      ...this.postFilters
    },
    details: {
      ...this.postDetails
    }
  }

  formData.append('post', JSON.stringify(post))
  formData.append('file', this.postMedia.file)

  postService.createOne(formData).then((res) => {
    if (res) {
      postServiceStatus.createOneDone()
    }
  });
}

If anyone has an idea, I'd be grateful!

CodePudding user response:

The server can consume an incoming request only once, because the request body is streamed by the client and the HTTP protocol does not allow the server to request a "rewind" from the client.

The idea of multer and formidable is to write the file to the uploadDir as the request is being consumed. If you do not want to do that in the first middleware, you need to store the file in a Javascript variable (for example, res.locals.uploadedFile) and write it to the uploadDir yourself in the second middleware. But what would be the advantage of that? Main memory is more expensive than disk space.

Why do you insist on two separate middlewares?

CodePudding user response:

I finally found a solution to my problem !

I created a global error handler that handle all the errors on my API, and when an error is related to my route where I created the file (in the first middleware), I delete the file with informations passed in req.file.

const fs = require('fs')
const { DEFAULT_API_PATH } = process.env

/**
 * Handle middlewares errors
 * @param {Object} err error object
 * @param {String} err.type type of error (service affected by the error)
 * @param {String} err.error original error
 * @param {String} err.message returned error
 * @param {Number} err.status error status
 * @returns 
 */
module.exports = async (err, req, res, next) => {
  if (err.error) console.log(err.error) 
  
  // Delete file
  if (req.path === `${DEFAULT_API_PATH}/posts/create` && req.file) {
    if (fs.existsSync(req.file.path)) {
      fs.unlinkSync(req.file.path, function (err) {
        console.log(err)
      }); 
    }
  } 
  
  if (err.type && err.type === 'database') return res.status(err.status || 400).send({ error: err.message || 'Une erreur innatendue s\'est produite avec la base de données. Veuillez réessayer dans un instant.' }) 
  else if (err.type && err.type === 'server') return res.status(err.status || 400).send({ error: err.message || 'Une erreur innatendue s\'est produite avec le serveur. Veuillez réessayer dans un instant.' }) 
  
  return res.status(err.status || 400).send({ error: err.message || 'Une erreur innatendue s\'est produite. Veuillez réessayer dans un instant.' }) 
}
  • Related