5

I have pdf documents stored in the file system on the server side.

I need to let the user download one of them when he/she clicks on download.

The problem is that I know how to send a file from NodeJS to browser but here the request will be made by a ReactJS axios request. So when I send a file, the response will go to react. How do I send that pdf file to the user? Do I access the file system directly using my front end code?

I get the following in the browser console when I log the response after I do res.sendFile(file_path) in NodeJS

enter image description here

How do I process this so that I can make the user download the pdf?

Sujal Patel
  • 2,444
  • 1
  • 19
  • 38
Abdul Ahad
  • 1,221
  • 2
  • 16
  • 28
  • are you using some reverse proxy like Nginx, as it is highly optimized for sharing static files? – mehta-rohan Jun 06 '19 at 08:42
  • No, I am not using Nginx. Actually my issue is to somehow allow the user to download the pdf file, but since the request is to be made by react, I cannot make him download the file. – Abdul Ahad Jun 06 '19 at 08:54
  • what is your end goal? – mehta-rohan Jun 06 '19 at 08:55
  • Once the user clicks on download, he/she should be able to download that particular file stored in my file system (whatever method I use) . Is it fine if I do not involve the server end in this ( I am not sure if it the correct thing to do)? – Abdul Ahad Jun 06 '19 at 08:56
  • 1
    there must be an option for sendFile in response, or you can send data back to client and generate pdf on client side – mehta-rohan Jun 06 '19 at 09:01
  • Yes, the sendfile is working, but what do I do to make the user download the file or how do I process the once I receive it. – Abdul Ahad Jun 06 '19 at 09:21
  • after receiving file you can render pdf file in your browser... if file returned in byte you have to convert it into browser readable file. – mehta-rohan Jun 06 '19 at 09:25
  • That is exactly what I am asking, I can send the file using res.sendFIle, after that how do I process it? The header says: cache-control: "public, max-age=0" content-type: "application/pdf" last-modified: "Thu, 06 Jun 2019 07:10:57 GMT" – Abdul Ahad Jun 06 '19 at 09:31

1 Answers1

9

You can use file-saver to download the file. Below is the function I'm using for pdf download. (response.data is the Buffer that nodejs sends back as a response)

import FileSaver from 'file-saver';
...

_onPdfFetched() {
  FileSaver.saveAs(
    new Blob([response.data], { type: 'application/pdf' }),
    `sample.pdf`
  );
}

or you can just show pdf to the user

window.open(response.data, '_blank');

Edit

The axios call should be like this:

axios.get(url, {
  responseType: 'arraybuffer',
  headers: {
      Accept: 'application/pdf',
  },
});

Edit 2

The nodejs code should be like this:

router.post('/api/downloadfile',(req, res, next) => {
  const src = fs.createReadStream('path to sample.pdf');

  res.writeHead(200, {
    'Content-Type': 'application/pdf',
    'Content-Disposition': 'attachment; filename=sample.pdf',
    'Content-Transfer-Encoding': 'Binary'
  });

  src.pipe(res); 
});
Vasileios Pallas
  • 4,801
  • 4
  • 33
  • 50
  • The download does take place but the pdf content is totally distorted. – Abdul Ahad Jun 06 '19 at 09:59
  • 1
    I told you, response.data is the Buffer that nodejs sends back as a response. You should send Buffer from nodejs – Vasileios Pallas Jun 06 '19 at 10:05
  • I searched a lot, but I did not get how to send the file buffer from node. I used file.pipe() and other measures but with the same result. Is there any document I could refer to or could you tell me how exactly is the buffer sent at the server side. – Abdul Ahad Jun 06 '19 at 10:30
  • can you provide the nodejs code in order to help you? – Vasileios Pallas Jun 06 '19 at 10:40
  • `router.post('/api/downloadfile',(req,res,next)=>{ pathname=path.join(__dirname,'../../../documents','sample.pdf'); res.sendFile(pathname); })` I have also tried this but to no avail: [link](https://stackoverflow.com/questions/10046039/nodejs-send-file-in-response) – Abdul Ahad Jun 06 '19 at 10:47
  • if you try to replace `res.sendFile(pathname)` with `res.attachment(pathname)` is it working? Because this one adds the correct headers `// Content-Disposition: attachment; filename="file.pdf"` `// Content-Type: application/json` or even better you can use `res.download(pathname)` which might do the job for you – Vasileios Pallas Jun 06 '19 at 11:08
  • I tried both with the following axios call but the same result: `axios.post('http://localhost:7000/api/downloadfile',{ responseType: 'arraybuffer', headers: { Accept: 'application/pdf', },}) .then(res=>{ console.log("RESPONSE IS:",res); FileSaver.saveAs( new Blob([res.data], { type: 'application/pdf' }), `name.pdf` ); }) .catch(err=>{ console.log("error",err); })` – Abdul Ahad Jun 06 '19 at 11:18
  • I edit my answer with the nodejs code. I hope this will help you – Vasileios Pallas Jun 06 '19 at 11:42
  • Tried it man, same result. Even with different pdfs, the content is totally lost. I have no clue what to do. For black and white pdfs, I just get blank pages. – Abdul Ahad Jun 06 '19 at 12:21
  • If you try to run your endpoint on postman what's the response? – Vasileios Pallas Jun 06 '19 at 13:05
  • With postman, I get the correct pdf. No data loss at all. My front end call: `axios.post('http://localhost:7000/api/downloadfile',{ responseType: 'arraybuffer', headers: { Accept: 'application/pdf', },}) .then(res=>{ console.log("RESPONSE IS:",res); FileSaver.saveAs( new Blob([res.data], { type: 'application/pdf' }), `name.pdf` ); }) .catch(err=>{ console.log("error",err); })` – Abdul Ahad Jun 06 '19 at 13:57
  • maybe the response doesn't need to be converted to `Blob`. Try just to pass the response data on `File saver` like this: `FileSaver.saveAs(response.data, 'sample.pdf');` – Vasileios Pallas Jun 06 '19 at 14:02
  • No, I get an error :Failed to execute 'open' on 'XMLHttpRequest': Invalid URL and looks like one needs to use blob with filesaver. – Abdul Ahad Jun 06 '19 at 14:14
  • 1
    ok I see what is happening. You are doing `axios.post`, not `axios.get`, which means that the options should be the third parameter (on post call the second parameter is the request body). So it should look like this: `axios.post(url, {}, { responseType: 'arraybuffer', headers: { Accept: 'application/pdf'}})` – Vasileios Pallas Jun 06 '19 at 14:53
  • worked like a charm, thanks a lot. So that was it? Wrong parameter list? And could you explain what is happening here, I mean I get that we are converting the pdf into a stream of bytes and then processing it. Is that all? – Abdul Ahad Jun 06 '19 at 14:56
  • 1
    Actually I believe the problem was the axios call. Now, about the nodejs code I gave you, it's for better usage of files using pipes. You can see [this article](https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/) – Vasileios Pallas Jun 06 '19 at 15:36
  • This answer got me a long ways. But for some reason the browser is not correctly opening the pdf file. My axios call looks like this: `axios .get('SDS/openPDFFile', { params: { pdf }, responseType: 'arraybuffer', headers: { Accept: 'application/pdf' } }) .then((res) => { console.log(res); window.open(res.data, '_blank'); });` And then the browser tries to open: `http://localhost:3000/[object%20ArrayBuffer]` – dmikester1 Jul 28 '20 at 16:23
  • @dmikester1 Did you try to convert to response to Blob? – Vasileios Pallas Jul 30 '20 at 14:27
  • I Use this way in Node Backend: ```res.setHeader("Content-Type", "application/pdf"); res.setHeader( "Content-Disposition", `attachment; filename= someFileName.pdf` ); res.end(data); ``` In the frontend using Axios: ``` axios.post("yourapi/blah/blah",{data: {somedata: somevalue},},{responseType:"arraybuffer",headers: {Accept: "application/pdf", }, } ) .then((result) => { FileDownload( new Blob([result.data], { type: "application/pdf" }), fileName ); }) ``` – rohitpaniker Aug 17 '20 at 16:27
  • I think you are missing the `'Content-Transfer-Encoding': 'Binary'` header in nodejs code – Vasileios Pallas Aug 17 '20 at 17:30