Search code examples
javascriptreactjspdfpuppeteernestjs

How to download pdf from puppeteer using Nest js as Server Side and React in Client Side?


I am using Nest in backend to generate a pdf file with Puppeteer. Puppeteer is working fine when I give it the path to create pdf on disk.

I am currently returning the pdf.

This is the code generating the pdf:

const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    await page.goto('https://blog.risingstack.com', {waitUntil: 'networkidle0'});


    var options = {
      width: '1230px',
      displayHeaderFooter: false,
      margin: {
        top: "10px",
        bottom: "30px"
      },
      printBackground: true,
    }

    const pdf = await page.pdf(options);
  
    await browser.close();
    return pdf

And this is the controller that calls the previous function:

  @Header('Content-Type', 'application/pdf')
  async Printpdf(@Body() message: any) {
    console.log(message);
    return this.PrintpdfService.printpdf();
  }

In React I am calling this with axios like this:

return axios.post(`http://localhost:3000/printpdf`,data, {
    responseType: 'arraybuffer',
    headers: {
      'Accept': 'application/pdf'
    }
  });

I am trying to download the pdf with this:

getBuildingReport(data).then((response) => {
      console.log(response);
      const blob = new Blob([response.data], {type: 'application/pdf'})
       const link = document.createElement('a')
       link.href = window.URL.createObjectURL(blob)
       link.download = `name.pdf`
       link.click();
    })
    .catch(err => {
      console.log(err)
    }); 

I followed this tutorial. https://blog.risingstack.com/pdf-from-html-node-js-puppeteer/#option3

But the downloaded pdf is build correctly and is imposible to open it as I get "Failed to load PDF document."


Solution

  • I figured this out for a project once and saved the snippet... The key is loading the PDF into a buffer, and then sending that back to the client.

    Here's an example function implemented in a NestJS service:

      async generatePDF(): Promise<Buffer> {
        const content = fs.readFileSync(
          path.resolve(__dirname, './templates/invoice.html'),
          'utf-8'
        )
    
        const browser = await puppeteer.launch({ headless: true })
        const page = await browser.newPage()
        await page.setContent(content)
    
        const buffer = await page.pdf({
          format: 'A4',
          printBackground: true,
          margin: {
            left: '0px',
            top: '0px',
            right: '0px',
            bottom: '0px'
          }
        })
    
        await browser.close()
    
        return buffer
      }
    

    Here's an example NestJS controller:

      @Get('/:uuid/pdf')
      async getInvoicePdfByUUID(
        @Param('uuid', ParseUUIDPipe) uuid: string,
        @GetUser() user: User,
        @Res() res: Response,
      ): Promise<void> {
    
        // ...
    
        const buffer = await this.invoicesService.generatePDF()
    
        res.set({
          // pdf
          'Content-Type': 'application/pdf',
          'Content-Disposition': 'attachment; filename=invoice.pdf',
          'Content-Length': buffer.length,
    
          // prevent cache
          'Cache-Control': 'no-cache, no-store, must-revalidate',
          'Pragma': 'no-cache',
          'Expires': 0,
        })
    
        res.end(buffer)
      }
    

    Note that the above assumes you're using NestJS with Express.

    Cheers!