1

I have two file inputs and button. When the button is pressed, it should send the two files to the server and wait to receive a file back (the two files processed server-side and the result is returned).

HTML

<input id='file-input1' type='file'>
<input id='file-input2' type='file'>
<button id='send-btn'>

JavaScript (Client)

var input1 = document.getElementById('file-input1');
var input2 = document.getElementById('file-input2');
var btn = document.getElementById('send-btn');
var file1 = null;
var file2 = null;

input1.addEventListener('change', () => file1 = input1.files[0]);
input2.addEventListener('change', () => file2 = input2.files[0]);

btn.addEventListener('click', () => {
    if (file1 === null || file2 === null) return;
    _sendfiles(file1, file2);
});

function _sendfiles(file1, file2) {
    let xmlhttp = new XMLHttpRequest();
    xml.open("PUT", "/process", true);
    xmlhttp.send({'file1': file1, 'file2': file2});
}

JavaScript (Server)

app.put('/process', (req, res) => {
    // Get files from request
    // Do stuff with them to generate a third file
    // Send generated file back
});

I'm not sure how to receive the files on the server-side, nor how to wait to receive the server's file on the client side. The use of third-party modules is discouraged but not completely out of the question. I'm also not married to the idea of using XMLHttpRequest().

Michael Bianconi
  • 5,072
  • 1
  • 10
  • 25

1 Answers1

2

To send files from the client I'd suggest using Fetch + FormData API for convenience:

const formData = new FormData()
formData.append('file1', file1)
formData.append('file2', file2)
fetch(`/api/companies/${id}/logo`, {
  method: 'PUT',
  body: formData
})

Passing FormData instance to body will automatically set Content-Type: multipart/form-data header.

On the server side I'd suggest using multer since you already use express. You can, of course, implement your own middleware to retrieve files from the request stream if you want to (I didn't do it myself so can't help much).

To receive a file on the client you can do the following (I assume you want this file to be downloaded to the user's file system):

Way #1 (simple):

In the response just send a download URL of this file. Then use this solution to create a link and trigger click event on it. The file will be downloaded by a browser.

Way #2 (not so simple):

On the server use res.sendFile method to send a file (if it's located on fs - otherwise you can send a file Buffer like this for instance).

Then on the client you have response.blob() method to access file blob. Use a similar trick to download this blob into a file with the help of URL.createObjectURL API.

Additionally, Response API allows you to pipe the stream and do other things with it if you need to (see Streams API).


EDIT (the simplest way)

As Endless pointed out there is a much simpler way actually. I guess I spent too much time dealing with AJAX requests... ‍♂️

You can just submit your HTML form by clicking on submit button, this way a browser will send POST (yeah, can't do PUT this way) request with Content-Type: multipart/form-data automatically since you have inputs with type file:

<form method='post' action='/process'>
  <input name='file1' type='file'>
  <input name='file2' type='file'>
  <button type='submit'>Submit</button>
</form>

So no need to set any event listeners or use any JS in fact.

Then on the server use res.sendFile and add a Content-Disposition: attachment; filename="filename.jpg" header to make sure browser will download it as an attachment and not open it as a webpage.

The biggest disadvantage here is that there is no built-in convenient way in a browser to subscribe to the request's completion event. I.e. there is no success event on the form which you can listen to.

So, if you need it then a nice approach would be to send a cookie from the server along with the file. On the client set an interval at the moment you submit the form and there check if the cookie exists. If it exists then this means the file is downloaded.

GProst
  • 9,229
  • 3
  • 25
  • 47
  • 1
    using `res.sendFile` and sending extra headers is pointless if you are going to save it with javascript anyway. I would recommend using `res.sendFile` and just navigate to the endpoint by just submitting the the form, it will trigger the download for you and then you don't need to deal with creating a blob, bloburl and a download link - not to mention all cross browser carvet you have to deal with. – Endless Jul 26 '19 at 12:39
  • Updated my answer – GProst Jul 26 '19 at 16:44