Uploading a file with FormData and multer
Solution 1
multer uses multipart/form-data
content-type requests for uploading files. Removing this bit from your doRequestSetHeaders
function should fix your problem:
if(method === "POST" || method === "PUT"){
r.setRequestHeader("Content-Type", "application/json");
}
You don't need to specify the content-type
since FormData
objects already use the right encoding type. From the docs:
The transmitted data is in the same format that the form's submit() method would use to send the data if the form's encoding type were set to multipart/form-data.
Here's a working example. It assumes there's a dropzone with the id drop-zone
and an upload button with an id of upload-button
:
var dropArea = document.getElementById("drop-zone");
var uploadBtn = document.getElementById("upload-button");
var files = [];
uploadBtn.disabled = true;
uploadBtn.addEventListener("click", onUploadClick, false);
dropArea.addEventListener("dragenter", prevent, false);
dropArea.addEventListener("dragover", prevent, false);
dropArea.addEventListener("drop", onFilesDropped, false);
//----------------------------------------------------
function prevent(e){
e.stopPropagation();
e.preventDefault();
}
//----------------------------------------------------
function onFilesDropped(e){
prevent(e);
files = e.dataTransfer.files;
if (files.length){
uploadBtn.disabled = false;
}
}
//----------------------------------------------------
function onUploadClick(e){
if (files.length){
sendFile(files[0]);
}
}
//----------------------------------------------------
function sendFile(file){
var formData = new FormData();
var xhr = new XMLHttpRequest();
formData.append("track", file, file.name);
xhr.open("POST", "http://localhost:3000/tracks/upload", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.send(formData);
}
The server side code is a simple express app with the exact router code you provided.
Solution 2
to post a FormData object accepted by multer the upload function should be like this:
function uploadFile(fileToUpload, url) {
var formData = new FormData();
//append file here
formData.append('file', fileToUpload, fileToUpload.name);
//and append the other fields as an object here
/* var user = {name: 'name from the form',
email: 'email from the form'
etc...
}*/
formData.append('user', user);
// This function simply creates an XMLHttpRequest object
// Opens the connection and sends form_data
doJSONRequest("POST", "/tracks/upload", null, formData, function(d) {
console.log(d);
})
}
Comments
-
nbro almost 4 years
I have successfully managed to upload a file to a Node server using the
multer
module by selecting the file using the input file dialog and then by submitting the form, but now I would need, instead of submitting the form, to create aFormData
object, and send the file usingXMLHttpRequest
, but it isn't working, the file is alwaysundefined
at the server-side (router).The function that does the AJAX request is:
function uploadFile(fileToUpload, url) { var form_data = new FormData(); form_data.append('track', fileToUpload, fileToUpload.name); // This function simply creates an XMLHttpRequest object // Opens the connection and sends form_data doJSONRequest("POST", "/tracks/upload", null, form_data, function(d) { console.log(d); }) }
Note that
fileToUpload
is defined and theurl
is correct, since the correct router method is called.fileToUpload
is aFile
object obtained by dropping a file from the filesystem to a dropzone, and then by accessing thedataTransfer
property of the drop event.doJSONRequest
is a function that creates aXMLHttpRequest
object and sends the file, etc (as explained in the comments).function doJSONRequest(method, url, headers, data, callback){ //all the arguments are mandatory if(arguments.length != 5) { throw new Error('Illegal argument count'); } doRequestChecks(method, true, data); //create an ajax request var r = new XMLHttpRequest(); //open a connection to the server using method on the url API r.open(method, url, true); //set the headers doRequestSetHeaders(r, method, headers); //wait for the response from the server r.onreadystatechange = function () { //correctly handle the errors based on the HTTP status returned by the called API if (r.readyState != 4 || (r.status != 200 && r.status != 201 && r.status != 204)){ return; } else { if(isJSON(r.responseText)) callback(JSON.parse(r.responseText)); else if (callback !== null) callback(); } }; //set the data var dataToSend = null; if (!("undefined" == typeof data) && !(data === null)) dataToSend = JSON.stringify(data); //console.log(dataToSend) //send the request to the server r.send(dataToSend); }
And here's
doRequestSetHeaders
:function doRequestSetHeaders(r, method, headers){ //set the default JSON header according to the method parameter r.setRequestHeader("Accept", "application/json"); if(method === "POST" || method === "PUT"){ r.setRequestHeader("Content-Type", "application/json"); } //set the additional headers if (!("undefined" == typeof headers) && !(headers === null)){ for(header in headers){ //console.log("Set: " + header + ': '+ headers[header]); r.setRequestHeader(header, headers[header]); } } }
and my router to upload files is the as follows
// Code to manage upload of tracks var multer = require('multer'); var uploadFolder = path.resolve(__dirname, "../../public/tracks_folder"); function validTrackFormat(trackMimeType) { // we could possibly accept other mimetypes... var mimetypes = ["audio/mp3"]; return mimetypes.indexOf(trackMimeType) > -1; } function trackFileFilter(req, file, cb) { cb(null, validTrackFormat(file.mimetype)); } var trackStorage = multer.diskStorage({ // used to determine within which folder the uploaded files should be stored. destination: function(req, file, callback) { callback(null, uploadFolder); }, filename: function(req, file, callback) { // req.body.name should contain the name of track callback(null, file.originalname); } }); var upload = multer({ storage: trackStorage, fileFilter: trackFileFilter }); router.post('/upload', upload.single("track"), function(req, res) { console.log("Uploaded file: ", req.file); // Now it gives me undefined using Ajax! res.redirect("/"); // or /#trackuploader });
My guess is that
multer
is not understanding thatfileToUpload
is a file with nametrack
(isn't it?), i.e. the middlewareupload.single("track")
is not working/parsing properly or nothing, or maybe it simply does not work withFormData
, in that case it would be a mess. What would be the alternatives by keeping using multer?How can I upload a file using AJAX and multer?
Don't hesitate to ask if you need more details.
-
nbro over 8 yearsDid I say that my form is multipart, i.e. I have other type of fields in my form..?
-
cviejo over 8 yearsI'm not sure sure i understand your question? "content-type" for that request is set to "'multipart/form-data; boundary=----WebKitFormBoundary...", as expected. Otherwise multer wouldn't even work with it in the first place. You can add further fields using formData.append and read them from req.body.
-
nbro over 8 yearsI haven't tried your code yet, but it really seems similar to mine, so I don't see why yours should work... could you please explain why should that work?
-
cviejo over 8 yearsTo answer that question you should either post your complete client side code (not just uploadFile) or try mine. Mine does, again, work. Tested with your exact router code.
-
nbro over 8 yearsI will post my
doJSONRequest
method implementation...See edit now. -
cviejo over 8 yearsAre you setting content-type manually in doRequestSetHeaders? That'd be one difference to my code and why mine works and yours doesn't.
-
nbro over 8 yearsYes, I will also post the implementation of that function, if you want..see edit.
-
cviejo over 8 yearsWell, that is the reason why your code isn't working and mine isn't. Let's move this to a chat, though.
-
cviejo over 8 yearsLet us continue this discussion in chat.