1

I am using Node to grab a PDF from the server and send it to my React frontend. Then I want to display that PDF in the browser in a new tab. It's working fairly well, except the URL of the new tab with the PDF is not ideal. The URL of the new tab looks like: blob:http://localhost:3000/71659 but I would like it to look like http://localhost:3000/71659.pdf. No 'blob' and with a pdf extension like when I would click on a pdf on the web just like the examples here: https://file-examples.com/index.php/sample-documents-download/sample-pdf-download/

My current code that handles the saving of the blob and opening it is this:

.then((res) => {
    console.log(res);
    const file = new Blob([res.data], {
        type: 'application/pdf'
    });
    //Build a URL from the file
    const fileURL = URL.createObjectURL(file);
    window.open(fileURL, '_blank');
});

And this is my Node route the sends the stream:

router.get('/openPDFFile', async (req, res) => {
    console.log('we got to the server!!  with: ', req.query);
    const pdfFilename = req.query.pdf;
    const pdfFilepath = `./path/to/pdf/${pdfFilename}`;
    router.get();
    const src = fs.createReadStream(pdfFilepath);

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

    src.pipe(res);
});

Now I'm wondering if instead of sending the stream over the wire and converting it to a blob, if I can just simply create a route to that PDF from Node. Something like /PDF/${pdfFilename}. And then my React will just open that URL in a new tab?

Update - Here is my latest Node route based on x00's answer:

router.get('/openPDFFile', async (req, res) => {
    console.log('we got to the server!!  with: ', req.query);
    const pretty_PDF_name = req.query.pdf;
    const pdfFilename = (await SDS.getPDFFileName({ pretty_PDF_name }))
        .dataValues.sheet_file_name;
    console.log('pdfFilename: ', pdfFilename);

    const cleanPDFName =
        pretty_PDF_name
            .substring(0, pretty_PDF_name.length - 4)
            .replace(/[ ,.]/g, '') + '.pdf';

    const pdfFilepath = '\\path\\to\\file\\' + pdfFilename;
    const fullFilePath = path.join(__dirname + '/../' + pdfFilepath);
    console.log(cleanPDFName, fullFilePath);
    router.get('/pdf/' + cleanPDFName, async (req, res) => {
        res.sendFile(fullFilePath);
    });
    // router.get('/pdf/' + cleanPDFName, express.static(fullFilePath));
    // const src = fs.createReadStream(pdfFilepath);
    //
    // res.writeHead(200, {
    //  'Content-Type': 'application/pdf',
    //  'Content-Disposition': 'inline; filename=sample.pdf'
    // });
    //
    // src.pipe(res);
    // return res.json({ fileuploaded: cleanPDFName });
});

I had seen the express.static way as well and was trying that too.

dmikester1
  • 1,374
  • 11
  • 55
  • 113
  • cant speak to the react part but I would do the following on the creation of a new resource ( mime type "application/pdf" ) which should be REST accessible: 1. link the fs file path to an ID. 2. Store the ID in a DB , associating the ID with other search/access keys as needed 3. from the app, list IDS as needed 4. from app GET a specific ID by reconstructing http URL that resolves to the file resource along with the above mime-type – Robert Rowntree Aug 04 '20 at 21:58
  • I think I'm actually doing most of that already!! :) I have the unique ID saved alongside the filename in the DB. On the front end, the user clicks on the filename and it makes a call to the node server passing that filename. So I'm struggling specifically with the Node code. I thought I read somewhere I could create a route in Express to the file. `app.get ('/pdf/filename.pdf')` Something like that... – dmikester1 Aug 05 '20 at 03:09
  • Do you really need that `blob` part? Why not just `window.open('/pdf/71659.pdf', '_blank');` Do you want some kind of preload or error handling or something? What are requirements then? – x00 Aug 05 '20 at 15:09
  • see this answer : https://stackoverflow.com/a/14788331/560435 using req.ID and a simple route with just the ID app.get('/openfile/:id', you can add a fetch to the DB , resolving the path and then either stream the pdf or an http 302 to redirect to the full url of the file – Robert Rowntree Aug 05 '20 at 16:19
  • @x00 I don't want the blob part, that is the whole basis of my question. `/pdf/71659.pdf` is not a valid path but I want to make it a valid path. – dmikester1 Aug 05 '20 at 16:38
  • @RobertRowntree I will look into that and let you know what I figure out. – dmikester1 Aug 05 '20 at 16:40
  • I meant not the `blob:...` part but `.then(res=> ... new Blob ...)` part. – x00 Aug 05 '20 at 17:28

3 Answers3

1

As I understood from the comments you don't have any special requirements (at least you didn't mention any when answering my comment). So you can just do this:

  1. client
    window.open(`/pdf/${pdfFilepath}`, '_blank');
    // no need for
    //   fetch('/openPDFFile', ... pdf: pdfFilepath ... })
    //     .then(res => ... Blob ... )
    // or whatever you where doing
    
  2. server
    router.get('/pdf/:pdfFilename', async (req, res) => {
      ...
      res.sendFile(__dirname + `/path/to/pdf/${req.params.pdfFilename}`)
    })
    

As a result you'll get url in the form of http://localhost:3000/pdf/71659.pdf. Also you can get the url without /pdf/ part, but I don't see any reason for that.

Update

About the colon: see "Route parameters" section here https://expressjs.com/en/guide/routing.html

Full working example:

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="get_pdf">Get PDF</div>
  </body>
  <script>
    // here can be your business logic
    // for example the name of pdf can be entered by the user through <input>
    const pdfFile = "1"

    // click will open new window with url = `http://localhost:3000/pdf/1.pdf`
    document
      .getElementById("get_pdf")
      .addEventListener("click", () => {
        window.open(`http://localhost:3000/pdf/${pdfFile}.pdf`, '_blank')
          // if you want the ".pdf" extension on the url - you must add it yourself
      })
  </script>
</html>
const express = require('express')
const app     = express()
app.get('/pdf/:pdf', async (req, res) => {
  const requested_pdf = req.params.pdf // === "1.pdf"
  console.log(requested_pdf)

  // here can be your business logic for mapping
  // requested_pdf from request to filepath of pdf
  // or maybe even to generated pdf with no file underneath
  // but I'll simply map to some static path
  const map_to_pdf_path = name => __dirname + `/path/to/pdf/${name}`

  res.sendFile(map_to_pdf_path(requested_pdf))
})
const listener = app.listen(process.env.PORT || constants.server_port, err => {
  if (err) return console.error(err)
  console.log(`Find the server at: http://localhost:${listener.address().port}`)
})
x00
  • 13,643
  • 3
  • 16
  • 40
  • This looks promising. I'll test it out and get back to you. – dmikester1 Aug 05 '20 at 18:41
  • Struggling to get this working, I'm trying to take the front end completely out of it. So I have my Node route I am just hitting with Postman and passing in the single parameter of the pdf filename. One thing I don't get is what does that colon do in the `router.get` call? – dmikester1 Aug 07 '20 at 17:00
  • @dmikester1 Why are you struggling? What's missing in my code for you? Use it as it is. Updated the answer with full working example. You only need to add your specific business logic with `SDS` and staff. – x00 Aug 07 '20 at 21:03
0

You can get a pretty filename if you hijack a bit of DOM for your purposes as indicated in this older solution, but you'll hit a number of issues in different browsers. The FileSaver.js project is probably your best bet for a near-universal support for what you're trying to accomplish. It handles blob downloads with names in a cross-browser way, and even offers some fallback options if you need IE <10 support.

James Tomasino
  • 3,520
  • 1
  • 20
  • 38
0

This is a EASY way to do it... By no means am I trying to say that this is a good way to do it; however.

You can change the name of the URL after it has loaded using:

 window.history.pushState("","","/71659.pdf");

Assuming that you can load the pdf by already going to that url, this is all you would have to do. (you wouldn't want people who are sharing that url to be sharing a broken url) Otherwise, you would need to make a new route that will accept your desired url.

If you want to you could add some error checking to see if the loaded url is the one that you want to change using:

window.location.href
Daniel
  • 1,392
  • 1
  • 5
  • 16