Multer: Empty `body` and `files` request properties

Created on 24 Oct 2016  路  14Comments  路  Source: expressjs/multer

I feel like I must be doing something extremely silly here, but can replicate my problem in a really simple app following your examples. Perhaps there is something really weird with my node setup.

Platform: OSX El Capitan
Node Version: v5.12.0 (I've also nvm-ed and tried with v6.7.0, same outcome)

Expected Result:
POST multipart/form-data requests with the multer middleware should add body and files properties to the request object with the data.

What happens for me:
Empty req.body or req.files data from POST requests. Multer throws no errors and returns empty object and array for them respectively.

I've made a simple repo that shows my setup: https://github.com/jamsinclair/multer-test

My server js is literally:
(I've tried using different api methods, .array(), .single('name'), .none() etc, still the same outcome)

var app = require('express')();
var multer = require('multer');

var upload = multer({ dest: './uploads' })

app.post('/upload', upload.any(), function (req, res, next) {
  console.log('body data:', req.body);
  console.log('files data:', req.files);
  res.sendStatus(200);
});

app.listen(9000, function () {
  console.log('Listening on port 9000');
});

Example payload being sent via Postman:

POST /upload HTTP/1.1
Host: localhost:9000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 40d74092-2b02-12c4-b213-6ecd515ff61f

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="foo"

bar
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

multer
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Most helpful comment

I ended up using solution from @wcandillon article
https://medium.com/@wcandillon/uploading-images-to-firebase-with-expo-a913c9f8e98d

const cors = require('cors')
const bodyParser = require('body-parser')
const Busboy = require('busboy')
const getRawBody = require('raw-body')
const contentType = require('content-type')

app.post(
  '/post-tweet',
  [
    // middlewares

    cors({ origin: true }),

    bodyParser.json(),

    bodyParser.urlencoded({
      extended: true,
    }),

    (req, res, next) => {
      if (
        req.rawBody === undefined &&
        req.method === 'POST' &&
        req.headers['content-type'].startsWith('multipart/form-data')
      ) {
        getRawBody(
          req,
          {
            length: req.headers['content-length'],
            limit: '10mb',
            encoding: contentType.parse(req).parameters.charset,
          },
          function(err, string) {
            if (err) return next(err)
            req.rawBody = string
            next()
          }
        )
      } else {
        next()
      }
    },

    (req, res, next) => {
      if (
        req.method === 'POST' &&
        req.headers['content-type'].startsWith('multipart/form-data')
      ) {
        const busboy = new Busboy({
          headers: req.headers,
        })

        var fileBuffer = new Buffer('')

        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
          file.on('data', data => {
            fileBuffer = Buffer.concat([fileBuffer, data])
          })

          file.on('end', () => {
            const file_object = {
              fieldname,
              originalname: filename,
              encoding,
              mimetype,
              buffer: fileBuffer,
            }

            req.file = file_object
          })
        })

        req.data = {}

        busboy.on('field', function(
          fieldname,
          val
          // fieldnameTruncated
          // valTruncated
          // encoding
          // mimetype
        ) {
          req.data[fieldname] = val
        })

        busboy.on('finish', function() {
          console.log('Done parsing form!')

          next()
        })

        busboy.end(req.rawBody)
      } else {
        next()
      }
    },
  ],
  function(req, res, next) {
    // request handler
    const file = req.file

    console.log(file)
  }
)

It will be much simpler to just use one middleware, but it doesn't work in Firebase cloud :(

var multer = require('multer');
var upload = multer({ dest: './uploads' })

app.post(
  '/post-tweet',
  upload.single('photo'),
  function(req, res, next) {
    // request handler
    const file = req.file

    console.log(file)
  }
)

All 14 comments

I am having this exact same issue. node 6.7.0, multer 1.1.0, express 4.14.0, body-parser 1.15.2, cors 2.7.1 (just in case that affects things)

Sorry, my multer is actually 1.2.0 and I just commented out cors and got the same result.

I can't quite figure it out, but have a feeling this is due to the way the request is being sent with my Postman or Node script, unrelated to multer.

A simple html form posts okay and outputs the body data correctly in the console

Will update my example
Edit: I've added a simple send multipart POST request script to my example repo that silently fails with multer. Seems perhaps an issue with the way I'm sending the request?

Is there a certain way it expects the request to be sent or a special Header?
Edit: Yes, the multipart/form-data body must use CLRF line endings and be sent as binary to retain the line endings. See http://stackoverflow.com/a/10765244/5179940

I have to apologize to everyone here because it is suddenly working for me yet I cannot for the life of me figure out what I did. There is no change that I can see that I have done to suddenly make this work where it wasn't before. I have tried taking away a few changes that I thought might be responsible but to no effect. It's just working for me. Sorry @jamsinclair but I'll be of no help to you. :( If I find the culprit I'll be sure and update this issue.

After much digging around finally stumbled upon my answers here http://stackoverflow.com/a/10765244/5179940

  • The body data for the multipart/form-data requests must end with CLRF(\r\n) line endings.
  • The body data boundaries have an extra -- prepended
    For example, The header with boundary defined:

Content-Type: multipart/form-data; boundary=--123456

Note how the body data boundaries have an extra -- prepended
Body Data:

```
----123456
Content-Disposition: form-data; name="foo"

Bar
----123456--
```

Issue resolved for myself.

Hi @jamsinclair !
I have the same issue in Firebase Cloud Functions. Can you share your final Node.js fix please?
Or do I need to change request data on client side?
Here is how I send data to api

let formData = new FormData()

formData.append('photo', file)
formData.append('status', status)

let options = {
  method: 'POST',
  body: formData,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'multipart/form-data',
  },
}

return fetch(`${API__URL}/post-tweet`, options)

Firebase Cloud Functions run on Node v6.14.0

@serhiipalash I don't think this old issue is your problem. Looks like you're POST-ing the file from the browser with fetch?

In that case, you don't need to set the multipart Content-Type header. This will be set automatically from the FormData object.

Hopefully this works:

let formData = new FormData()

formData.append('photo', file)
formData.append('status', status)

let options = {
  method: 'POST',
  body: formData
}

return fetch(`${API__URL}/post-tweet`, options)

@jamsinclair no it didn't help.

The bug is only in Firebase Cloud Functions. When I emulate them locally with localhost Api url everything works. I am not sure why.

@serhiipalash @jamsinclair i'm having same problem.
When i run locally(firebase functions) it works, but it always empty when it deployed.

So, i used express-multipart-file-parser but it wasn't work ethier. Reference

I ended up using solution from @wcandillon article
https://medium.com/@wcandillon/uploading-images-to-firebase-with-expo-a913c9f8e98d

const cors = require('cors')
const bodyParser = require('body-parser')
const Busboy = require('busboy')
const getRawBody = require('raw-body')
const contentType = require('content-type')

app.post(
  '/post-tweet',
  [
    // middlewares

    cors({ origin: true }),

    bodyParser.json(),

    bodyParser.urlencoded({
      extended: true,
    }),

    (req, res, next) => {
      if (
        req.rawBody === undefined &&
        req.method === 'POST' &&
        req.headers['content-type'].startsWith('multipart/form-data')
      ) {
        getRawBody(
          req,
          {
            length: req.headers['content-length'],
            limit: '10mb',
            encoding: contentType.parse(req).parameters.charset,
          },
          function(err, string) {
            if (err) return next(err)
            req.rawBody = string
            next()
          }
        )
      } else {
        next()
      }
    },

    (req, res, next) => {
      if (
        req.method === 'POST' &&
        req.headers['content-type'].startsWith('multipart/form-data')
      ) {
        const busboy = new Busboy({
          headers: req.headers,
        })

        var fileBuffer = new Buffer('')

        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
          file.on('data', data => {
            fileBuffer = Buffer.concat([fileBuffer, data])
          })

          file.on('end', () => {
            const file_object = {
              fieldname,
              originalname: filename,
              encoding,
              mimetype,
              buffer: fileBuffer,
            }

            req.file = file_object
          })
        })

        req.data = {}

        busboy.on('field', function(
          fieldname,
          val
          // fieldnameTruncated
          // valTruncated
          // encoding
          // mimetype
        ) {
          req.data[fieldname] = val
        })

        busboy.on('finish', function() {
          console.log('Done parsing form!')

          next()
        })

        busboy.end(req.rawBody)
      } else {
        next()
      }
    },
  ],
  function(req, res, next) {
    // request handler
    const file = req.file

    console.log(file)
  }
)

It will be much simpler to just use one middleware, but it doesn't work in Firebase cloud :(

var multer = require('multer');
var upload = multer({ dest: './uploads' })

app.post(
  '/post-tweet',
  upload.single('photo'),
  function(req, res, next) {
    // request handler
    const file = req.file

    console.log(file)
  }
)

@serhiipalash You saved me. It works well.
ty bro.

My source

np )

if (
req.method === 'POST' &&
req.headers['content-type'].startsWith('multipart/form-data')
) {
const busboy = new Busboy({
headers: req.headers,
})

    var fileBuffer = new Buffer('')

    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
      file.on('data', data => {
        fileBuffer = Buffer.concat([fileBuffer, data])
      })

      file.on('end', () => {
        const file_object = {
          fieldname,
          originalname: filename,
          encoding,
          mimetype,
          buffer: fileBuffer,
        }

        req.file = file_object
      })
    })

    req.data = {}

    busboy.on('field', function(
      fieldname,
      val
      // fieldnameTruncated
      // valTruncated
      // encoding
      // mimetype
    ) {
      req.data[fieldname] = val
    })

    busboy.on('finish', function() {
      console.log('Done parsing form!')

      next()
    })

    busboy.end(req.rawBody)
  } else {
    next()
  }
}

Thank you Bro. You saved my life.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NobleUplift picture NobleUplift  路  4Comments

samipjain picture samipjain  路  4Comments

tonghae picture tonghae  路  4Comments

trexanhvn picture trexanhvn  路  3Comments

ChristianRich picture ChristianRich  路  4Comments