0

I am working on a Node.JS server using Express to generate and download PDFs based on user input. I call the server using Axios POST, and then am expecting the file to be downloaded using response.download() from Express. I recently changed from using the <form action=""> method of calling my API to Axios because Netlify doesn't seem to support NuxtAPI. The program produced the desired files in the old version, however something about the change to Axios means that it no longer works.

Now, no file is being downloaded (even fixed-path ones such as on published websites), and the page is reloading before even being prompted for the download.

My code is as below:

print.vue:


<template>
<main>
     <button class="btn btn-primary" @click="submit">Print Certificate</button>
</main>
</template>
<script>
export default{
methods:{
        async submit(){
            try{
                let params = {
                    name: this.name,
                    kana: this.kana,
                    rank: this.rank,
                    date: this.date
                }
                // produces valid object in all cases
                await this.$axios.$post('/api/certificate', null, {params})
            }catch(err){
                console.log(err)
                alert(err)
            }
        }
    },
    computed:{ // functions from route params, resolve to these always for test case
    name: function(){return 'Test Student'},
    kana: function(){return "Tesuto Sutuudento"},
    rank: function(){return "8th Kyu"},
    date: function(){return '26th October, 2020"}
    }
}
</script>

/api/certificate.js

var urlencodedParser = bodyParser.urlencoded({ extended: false })
app.post('/', urlencodedParser, function(req, res) {
    let response = {
      name: req.query.name,
      kana: req.query.kana,
      rank: req.query.rank,
      date: req.query.date
    }
    console.log(response)
    html = html.replace("{{name}}", response.name)
    html = html.replace("{{kana}}", response.kana)
    html = html.replace("{{rank}}", response.rank)
    html = html.replace("{{date}}", response.date) // always produces valid string (template)
    pdf.create(html, options).toFile('static/certificates/' + response.name.split(' ')[0] + '_' + response.rank.split(' ')[0] + '_' + response.rank.split(' ')[1] + '.pdf', function(err, rep){
        // file made with no difficulties
        if (err) return console.log(err);
        console.log(rep.filename) // gives absolute path (C:\\)
        //res.download('https://get.pxhere.com/photo/computer-screen-technology-web-internet-signage-page-coding-website-html-programming-font-design-text-digital-information-java-site-games-software-development-code-screenshot-data-script-www-css-computer-programming-web-developer-web-development-programming-code-web-page-website-development-670370.jpg') // test fixed path downloading
        //res.download(rep.filename,'my_pdf.pdf') // what I think the easiest solution should be
        res.download('static/certificates/' + response.name.split(' ')[0] 
        + '_' + response.rank.split(' ')[0] + '_' 
        + response.rank.split(' ')[1] + '.pdf') // what worked before
    })
})

I'm relatively new to APIs and Node in general, so is there something fundamental that I'm missing?

awsirkis
  • 389
  • 6
  • 19
  • I thnik you can use `fs` module of node which is very use full and use `fs.writeFileSync` function to write data in pdf or any format https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options – Aryan Oct 26 '20 at 10:42
  • How does your form looks like ? It sounds like the page got reloaded because there is a default action executed on the client side. Does your server side code work if you use a tool like `curl` or postman to produce a http request ? – Marc Oct 26 '20 at 11:20
  • @Marc No form is used, although it stopped reloading this morning. All of the data is computed from the URL params – awsirkis Oct 26 '20 at 19:21
  • Where is "this.name" defined ? Where is your "
    " ? How does your html look like ?
    – Marc Oct 26 '20 at 19:22
  • @Marc this.name and such are Nuxt computed properties based on the route parameters. I have included the code as simply returning what they resolve to for my test case. I have edited to include the relevant markup – awsirkis Oct 26 '20 at 19:38
  • @Arya there's no problem creating files, just downloading them – awsirkis Oct 26 '20 at 21:28

2 Answers2

0

It turns out that Axios doesn't like downloading anything besides TXT, HTML, or JSON files, so we need to download the produced file as an arraybuffer, and then use js-file-download to turn it into our desired file format.

Example below:

in my server

import Path from 'path'
import pdf from 'html-pdf'

    pdf.create(html, options).toFile('./mypdf.pdf', function(err, rep){
        if (err) return console.log(err);
        let path = Path.resolve(rep.filename) // ensures we always get the generated path
        console.log("resoved path:" + path)
        res.setHeader('Content-disposition', 'mypdf.pdf');
        res.download(path)

    })

In my client:

methods:{
        async submit(){
            const fileDownloader = require('js-file-download');
            try{
                let params = {
                    name: this.name,
                    kana: this.kana,
                    rank: this.rank,
                    date: this.date
                }
                // use responseType to tell axios its a buffer, using $post instead of post is shorthand for post().data
                // not setting this header results in corrupted pdfs
                let response = await this.$axios.$post('/api/certificate', null, {params: params, responseType:'arraybuffer'})
                console.log(response) // gibberish because Adobe
                // create and immediately download. Since we don't want to give a choice, immediate download is fine 
                fileDownloader(response, 'mypdf.pdf') 
                
            }catch(err){
                console.log(err)
                alert(err)
            }
        }
    },

Special thanks to this answer for client-side headers, this answer for a link to js-file-downloader and this answer for server-side response headers. Wouldn't have thought it was this tricky.

awsirkis
  • 389
  • 6
  • 19
-1

I think this will work

know more about writeFileSync


const data = {
    Patient_name: result.patient_name,
    Doctor: result.doctor,
    Date: result.date,
    Time: result.time,
    Key: result.key
};
const path = `${process.cwd()}/public/UserData.csv`;
fs.writeFile(path, data, function (err) {
    if (err) { throw err; }
    else {
        res.download(path);
        console.log(data);
    }
})

Aryan
  • 3,338
  • 4
  • 18
  • 43
  • you have to write `res.download()` – Aryan Oct 26 '20 at 11:21
  • And what does res.download send to the client if "pdf" is undefined ?! Read my comment and the docs again. https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback. Please delete your answer and think again about the problem. The sync version has no callback... – Marc Oct 26 '20 at 11:23
  • i have read and if data is coming from form it will create or if there is already a file called my.pdf it will add data in that pdf – Aryan Oct 26 '20 at 11:25
  • Still "dosnt work", because the callback you passed gets never called. And thats bad practice to serve a hardcoded file for every user. What do you do with concurent requests ? User B gets the pdf from User A. Terrible! – Marc Oct 26 '20 at 11:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/223623/discussion-between-marc-and-arya). – Marc Oct 26 '20 at 11:31