14

I have an API written with node.js hosted on heroku and my frontend app is written in Vue.js, it is on hostinger. I would like to know if is it possible to generate a PDF file with puppeteer and send it immediately to frontend client without saving it to the disk first? If yes, could you give me some example on how to do it?

Currently my function is like that:

exports.gerarPDFAvaliacao = async (dadosAvaliacao) => {
    try {
        const compile = async (fileName, data) => {

            const filePath = path.join(process.cwd(), 'src/templates/client/operation/', `${fileName}.hbs`);
            const html = await fs.readFile(filePath, 'utf-8');
            return await hbs.compile(html)(data);
        }

        const browser = await puppeteer.launch();
        const page = await browser.newPage();

        let content = await compile('avaliations', dadosAvaliacao);

        await page.goto(`data:text/html,${content}`, { waitUntil: 'networkidle0' });
        await page.emulateMedia('screen');
        await page.pdf({
            path: 'src/dist/pdf/' + dadosAvaliacao.arquivo + '.pdf',
            format: 'A4',
            printBackground: true
        })
        await browser.close();

        return dadosAvaliacao.arquivo + '.pdf';
    } catch (error) {
        console.log('Errors => ', error);
    }
};
Tolga Evcimen
  • 7,112
  • 11
  • 58
  • 91
Felipe Paz
  • 347
  • 1
  • 6
  • 19

1 Answers1

29

According to official documentation, if you do not provide a path the file will not be saved to disk.

page.pdf(options) : Options object which might have the following properties: path The file path to save the PDF to. If path is a relative path, then it is resolved relative to current working directory. If no path is provided, the PDF won't be saved to the disk.

This mean that it should return something like a buffer or a binary representation of the generated file. You just need to return that or pipe that to the response,depending on the framework you are using.

This just outputs the pdf to console:

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');

console.log(await page.content());
const pdf = await page.pdf();

await browser.close();
console.log(pdf) // maybe do response(pdf).type('x-pdf')

EDIT: Here is a complete example using express and puppeteer that returns the pdf content directly in-memory. It runs on runkit, so I think the same limitations apply (maybe even more). If you navigate to the public endpoint that the example has you can see how the browser detects that it is a pdf file and renders it properly.

code

const puppeteer = require ('puppeteer');
const express = require('express');
var app = express(exports);
const browser = await puppeteer.launch();

const main = async () => {

    const page = await browser.newPage();
    await page.goto('https://example.com');

    const pdf = await page.pdf();
    return pdf;
 }


app.get('/', async function (req, res) {
        const pdf = await main();
        res.contentType("application/pdf");
        res.send(pdf);
});

app.listen(3000, function(){ console.log('Listening on 3000') });
Danielo515
  • 5,996
  • 4
  • 32
  • 66
  • Danielo515, I've edited my question and inserted my function. As you can see, I'm using handlebars to generate my template and then, I pass it to to puppeteer to create the pdf file. Then lastly I return the path of file to the front. – Felipe Paz Nov 06 '18 at 12:15
  • Yes, you can do that. But as I show on my example, if you do not provide a path the file will be generated in memory and you can save it to a variable and just return it with the proper encoding. Both ways work, but you were asking for a way that does not require to save the file to disk. – Danielo515 Nov 06 '18 at 12:39
  • Thanks to you, I'm almost there. Now, I can pass the buffer to my front but I don't know how to render this buffer as a pdf file in my front app ( vuejs)!! =( – Felipe Paz Nov 06 '18 at 12:43
  • If you are triggering a get on your front app depending on the returned mime-type from the server the browser may automatically download the file. If you are using express it may be as simple as just `res.header('Content-type', 'application/pdf');` before doing `res(content)`. If that does not work you can always download as a blob on the client side by creating a link on the fly. Here is an example that I used for very big CSV files: https://stackoverflow.com/a/31438726/1734815 – Danielo515 Nov 06 '18 at 13:00
  • I've tried what you said but I got a blank pdf. In your example, I change your `var blob = new Blob([text], {type: "text/plain"});` for `var blob = new Blob([res.data], {type: "application/pdf"});` and it's created an empty file. In my api: `let buffer = await avaliacaoPDF.gerarPDFAvaliacao(register);` and it sends `res.status(200).header('Content-type', 'application/pdf').send(buffer);` – Felipe Paz Nov 06 '18 at 13:26
  • I recommend you to write a simple GET endpoinit, without auth and anything and just navigate to it directly with your browser. You should get an empty page and a pdf downloaded. If you are using express, here is a complete express example: https://github.com/zishon89us/node-cheat/blob/master/pdf/pdf_browser/app.js In the example they read the file from disk, in your case that is not necessary because you have it already on a variable – Danielo515 Nov 06 '18 at 13:30
  • I cannot save it in disk because I'm using heroku to host my api, so, I have to pass it as a buffer to the front and in the front I can download it like a pdf file. What you said it's exactly how I was using it locally but heroku isn't able to keep files in disk. – Felipe Paz Nov 06 '18 at 13:58
  • What I was providing was just an example of how to properly return a PDF with express. I was suggesting to mix what you already have with that. Right now I'm busy, but I'll try to provide a complete example tonight – Danielo515 Nov 06 '18 at 14:03
  • Ok Danielo, thanks a lot. Anyway, You've gave me a north to do that. – Felipe Paz Nov 06 '18 at 14:12
  • @FelipePaz I just added the complete example I was talking about. Hope it is clearer now – Danielo515 Nov 07 '18 at 12:25
  • 1
    Thanks a lot for your attention, Danielo. I've solved changing the buffer to base64. So, I receive the buffer and I convert it to base64. In front I create a new Blob and the pdf is created. – Felipe Paz Nov 07 '18 at 15:54
  • @FelipePaz can you share how you did it in Express, i am running through same problem. – Luzan Baral Sep 10 '20 at 05:55