1

I am trying to upload a file to a Node backend that uses Multer. Multer requires the form to be submitted a specific way. If it's not submitted that way, the request.file parameter will be undefined. I have created an approach that works by using brute force. That working approach looks like this:

index-1.html:

<form method="POST" enctype="multipart/form-data" id="fileUploadForm">
  <input type="file" id="selectedFile" name="selectedFile" /><br/><br/>
  <input type="submit" value="Submit" id="btnSubmit"/>
</form>
...
var btn = document.getElementById('btnSubmit');
btn.addEventListener('click', function(e) {
  e.preventDefault();

  var form = $('#fileUploadForm')[0];
  var data = new FormData(form);
  console.log(data);

  $.ajax({
    type: "POST",
    enctype: 'multipart/form-data',
    url: "/upload",
    data: data,
    processData: false,
    contentType: false,
    cache: false,
    timeout: 600000,
    success: function (data) {
      console.log("SUCCESS!");
    },
    error: function (e) {
      console.log("ERROR : ", e);
    }
  });
});        

The code above successfully posts the file to my server. That server has the following:

server.js

app.post('/upload', upload.single('selectedFile'), function(req, res) {
  if (req.file) {
    console.log('file uploaded');
  } else {
    console.log('no file');
  }

  res.send({});
});

Using the code above, "file uploaded" is displayed in the console window as expected. However, I need a more dynamic approach. For that reason, I need to programmatically build the form in JavaScript. In an attempt to do that, I've created the following:

index-2.html [my UI]

var btn = document.getElementById('btnSubmit');
btn.addEventListener('click', function(e) {
  var form = document.createElement('form');
  form.action = '/upload';
  form.method = 'POST';
  form.enctype = 'multipart/form-data';

  var node = document.createElement("input");                        
  node.name = 'selectedFile';
  node.value = GLOBAL_SELECTED_FILE;
  form.appendChild(node);

  var data = new FormData(form);
  data.append('id', this.id);
  console.log(data);

  $.ajax({
    type: 'POST',
    url: '/profile-picture/upload',
    enctype: 'multipart/form-data',                          
    data: data,                            
    contentType: false,
    processData: false,
    success: function(res) {
      console.log('success!');
    },
    error: function(xhr, status, err) {
      console.log('error');
    }
  });
});

This second approach does not work. To clarify, the GLOBAL_SELECTED_FILE variable is the data of a file that was selected from an input element. The data is loaded via the FileReader api. That looks like this:

var GLOBAL_SELECTED_FILE = null;

var fileReader = new FileReader();      
fileReader.onload = function(e) {
  GLOBAL_SELECTED_FILE = e.target.result;
}
fileReader.readAsDataURL(fileSelected);  // file selected comes from the onchange event on a <input type="file">..</input> element

Basically, I'm loading a preview of the image. Anyways, when I hit the submit button in the working version (index-1.html), I notice in Fiddler that a different value is sent over the value sent in index-2.html.

With the approach in index-1.html, Fiddler shows something like this in the "TextView" tab:

------WebKitFormBoundary183mBxXxf1HoE4Et
Content-Disposition: form-data; name="selectedFile"; filename="picture.PNG"
Content-Type: image/png

 PNG

However, when I look in the "TextView" tab in Fiddler for the data sent via index-2.html, I see the following:

------WebKitFormBoundary9UHBP02of1OI5Zb6
Content-Disposition: form-data; name="selectedFile"

data:image/png;base64,iVBORw0KGg[A LOT MORE TO GO]

It's like the FormData is using two different encodings for the same value. Yet, I don't understand why. How do I get index-2.html to send the image in the same format as index-1.html so that Multer will populate the req.file property?

Thank you!

user70192
  • 13,786
  • 51
  • 160
  • 240

1 Answers1

0

In index-1.html, you are using a file input:

<input type="file" id="selectedFile" name="selectedFile" />

In index-2.html, you are creating an ordinary form input (not a file input):

var node = document.createElement("input");                        
node.name = 'selectedFile';
node.value = GLOBAL_SELECTED_FILE;
form.appendChild(node);

To create a file input, you would need to add node.type = 'file'. However, you won't be able to set the value because browser security restrictions prevent setting the value of file inputs.

Instead, what you need to do is append the file that was selected by the user to the FormData object:

var data = new FormData(form);
data.append('id', this.id);
data.append('selectedFile', $('#fileInputElement')[0].files[0]);
user1091949
  • 1,933
  • 4
  • 21
  • 27