0

I have a unique situation in terms of difficulty.

I need to send HTML to the server, have the server convert the HTML to a PDF, send that PDF back to the client, and then download the PDF using client-side code.

I have to do it this way because I'm using client-side routing, so the only way I can access my endpoint that should perform this action is via a GET Request with Ajax or Fetch from client-side JavaScript. I am aware of res.sendFile(), but that attempts to render the file in the browser - I don't want that - rather, I want to be able to use client-side code to download the file.

Is it possible, then, to send a PDF file from temporary storage on the server down to the client, allowing client-side code to do whatever it wants to the file thereafter - in my case, downloading it?

I don't believe I have to provide any code because this is more of a theoretical question.

  • [`res.download()`](http://expressjs.com/fr/api.html#res.download) – Azami Feb 01 '19 at 03:22
  • What issue are you having implementing the requirement? – guest271314 Feb 01 '19 at 03:24
  • @MadWard I make a Fetch request to the endpoint from client-side JS, so res.download() wouldn't work. –  Feb 01 '19 at 03:24
  • @guest271314 The issue is that I don't know how to do it. –  Feb 01 '19 at 03:25
  • 1
    @JamieCorkhill [How to build PDF file from binary string returned from a web-service using javascript](https://stackoverflow.com/questions/12876000/how-to-build-pdf-file-from-binary-string-returned-from-a-web-service-using-javas/31763030), [How to download a file without using element with download attribute or a server?](https://stackoverflow.com/questions/38711803/) – guest271314 Feb 01 '19 at 03:27
  • @guest271314 My problem is that in the fetch request, I need to send in the body to the server the HTML I want turned into a PDF. Then I need to send that new PDF to the client, getting the response in the promise from fetch, and then downloading the response. –  Feb 01 '19 at 03:31
  • @JamieCorkhill What part of the procedure is the issue? – guest271314 Feb 01 '19 at 03:32
  • @guest271314 Implementation of the procedure, notably, if I have a file on the server, how do I send that file to the client. Then, on the client, how do I get the response? Does the Fetch API have a way to receive a file from a server? –  Feb 01 '19 at 03:34
  • @JamieCorkhill You write the code that performs those tasks. – guest271314 Feb 01 '19 at 03:35
  • @guest271314 Of course. But what code? Can I use Ajax or Fetch to receive a file from the server? –  Feb 01 '19 at 03:36
  • @JamieCorkhill Yes, you can use `XMLHttpRequest` or `fetch` or other available options, including an HTML `
    `, `EventSource`, `WebSocket`, etc. Provide a means to `POST` the HTML from client to server, use server side code to convert the HTML to PDF, serve the response to client. Offer the file for download at the client. See also [Downloading a file (or handling an HTTP error) on click](https://stackoverflow.com/q/38856623/).
    – guest271314 Feb 01 '19 at 03:38
  • @guest271314 I don't want to take up to much of your time, but could you perhaps add an answer describing what code you would use with Express to send the file to the client, and how you would use Fetch or XMLHttpRequest on the client to receive the response - the response being the file. –  Feb 01 '19 at 03:41
  • @JamieCorkhill Have not tried Express. You can use one of the approaches at previous links to offer response as download to client. – guest271314 Feb 01 '19 at 03:42
  • @guest271314 Alright. Thanks. I knew you could receive HTTP Responses from the server (and then use res.data() in the promise from Fetch to access it), but I didn't know if you could send an actual PDF file from the server, and get that file in the response. –  Feb 01 '19 at 03:46
  • @JamieCorkhill Yes, a PDF file can be served to client. – guest271314 Feb 01 '19 at 03:47
  • @guest271314 I have a solution. I just posted it as an answer. –  Feb 01 '19 at 19:18

1 Answers1

2

My issue stemmed from the fact that I could not just use res.sendFile() or res.download() from Express because the route was not being accessed by the browser URL bar, rather, my application uses client-side routing, and thus I had to make an HTTP GET Request via Fetch or XMLHttpRequest.

The second issue is that I needed to build the PDF file on the server based on an HTML string sent from the client - so again, I need to make a GET Request sending along a request body.

My solution, then, using Fetch, was to make the Get Request from the client:

fetch('/route' , {
    method: 'GET',
    body: 'My HTML String'
});

On the server, I have my code that converts the HTML string to a PDF, using the HTML-PDF Node module, and then, I convert that file to a Base64 String, setting the MIME Type and appending data:application/pdf;base64,.

app.get('/route', (req, res) => {
    // Use req.body to build and save PDF to temp storage (os.tempdir())
    // ...
    fs.readFile('./myPDF.pdf', (err, data) => {
        if (err) res.status(500).send(err);
        res.contentType('application/pdf')
           .send(`data:application/pdf;base64,${new Buffer.from(data).toString('base64')}`);
    });
});

Back on the client, I have my aforementioned Fetch Request, meaning I just need to tack on the promise to get the response:

fetch('/route', {
    method: 'POST',
    body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
    // Now I just need to download the base64String as a PDF.
});

To make the download, I dynamically create an anchor tag, set its href attribute to the Base64 String in the response from the server, give it a title, and then programmatically click it:

const anchorTag = document.createElement('a');
anchorTag.href = base64String;
anchorTag.download = "My PDF File.pdf"; 
anchorTag.click();

So, all together and on the client:

fetch('/route', {
    method: 'POST',
    body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
    const anchorTag = document.createElement('a');
    anchorTag.href = base64String;
    anchorTag.download = "My PDF File.pdf"; 
    anchorTag.click();
});

The solution for using an anchor tag to trigger the download came from another StackOverflow answer. It's also important to note that Base64 Encoding is not very efficient. Better solutions exist, but for my purposes, Base64 will work fine.

It is also imperative to note that Base64 Encoding is precisely that - an Encoding Scheme, not, I repeat, not an Encryption Scheme. So if your PDF files contain privileged information, you would likely want to add token authentication to the endpoint and encrypt the file.