While working with PSPDFKit for Web, you may find yourself needing to download multiple files from a server — perhaps to merge documents in a document operation, or to create multiple image annotations. Since the corresponding API methods require that you pass the data of these files — passing their URLs isn’t enough — you normally need to perform multiple requests to retrieve those files.
However, it’s also possible to gather all those files with a single request, provided you have control over the server. In this post, we’ll see how it’s done on the server side and the client side.
Overview
Uploading multiple files to a server using JavaScript is done by appending the files to a FormData
object:
const formData = new FormData(); formData.append('file1', blob1, 'file1.pdf'); // blob1 is a `Blob` object. formData.append('file2', blob2, 'file2.pdf'); // blob2 is another `Blob` object. fetch('https://example.com/upload/', { method: 'post', body: formData, });
But how can we get multiple files from a server with a single request? Well, for starters, we need a server capable of responding with a multipart/form-data
response. Spoiler: They usually aren’t.
You read that right. While sending multipart form data requests to the server is a common task performed everywhere, sending responses from the server with that same encoding is much less common. That said, let’s see how we can do it.
Server Side
There are different libraries and APIs available for each programming language, and they enable you to work with HTTP headers and responses. In this article, we’ll use JavaScript for both the browser-side and server-side code examples so that the similarities with and differences to the client code become more evident.
What follows is an example of how a Node.js server could send a multipart form data response to a browser. The Node.js environment doesn’t include a built-in FormData
class like the browser environment does, so we need another way to convert our file data to the multipart/form-data
format.
Luckily, there’s an npm package — form-data
— that will help us reshape our file data in the required way:
const fs = require('fs'); const http = require('http'); const FormData = require('form-data'); const app = http.createServer((req, res) => { console.log(`Request: ${req.url}`); const form = new FormData(); form.append('file1', fs.readFileSync('./path/to/file1.pdf'), { filename: 'file1.pdf', contentType: 'application/pdf', knownLength: fs.statSync('./path/to/file1.pdf').size, }); form.append('file2', fs.readFileSync('./path/to/file2.pdf'), { filename: 'file2.pdf', contentType: 'application/pdf', knownLength: fs.statSync('./path/to/file2.pdf').size, }); res.writeHead(200, { 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, 'Content-Length': form.getLengthSync(), 'Access-Control-Allow-Credentials': 'true', 'Access-Control-Allow-Origin': 'https://example.com', }); res.write(form.getBuffer()); res.end(); }); const port = 4000; app.listen(port); console.log(`Server listening in port ${port}`);
Just connecting to the server above will trigger a response that includes two files.
Client Side
Now, dealing with a multipart form data response from the browser is something we already know how to do; we just need to combine two interfaces: Response
and FormData
(the latter being implemented by the former, at least in modern browsers).
Let’s apply the FormData
interface to the server Response
:
try { const res = await fetch('https://example.com/download-multiple', { mode: 'cors', headers: { Accept: 'multipart/form-data', }, }); const formData = await res.formData(); console.log(Array.from(formData.values())); } catch (error) { console.error(error.message); }
The snippet above should log an array of objects with a key and a value (a file object) to the console.
Browser Support
As mentioned above, all modern browsers support the FormData
interface for the Response
object, with some exceptions: Some old Safari versions may throw if the response’s formData()
method is called, while IE11 doesn’t know anything about Response
objects at all: The usual polyfills also aren’t prepared to handle binary data in the FormData
response, so the only option in this case would be to parse the raw multipart body, which may or may not be worth the effort, depending on each scenario. In any case, it falls beyond the scope of this article.
Conclusion
As we’ve seen, downloading multiple binary files by sending a single request to the server can be accomplished quickly and easily, and at PSPDFKit, we’ve been doing it in our JavaScript PDF library for some time now. If you’re interested in learning more about our product, we encourage you to check out our demo and give it a try.