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--
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
multipart/form-data requests must end with CLRF(\r\n) line endings. -- prepended
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)
}
)
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.
Most helpful comment
I ended up using solution from @wcandillon article
https://medium.com/@wcandillon/uploading-images-to-firebase-with-expo-a913c9f8e98d
It will be much simpler to just use one middleware, but it doesn't work in Firebase cloud :(