Jakub Wąsowski
Jakub Wąsowski

Reputation: 41

Puppeteer saves PDF file correctly in local directory but when trying to open it from a Blob response it says that file is damaged

Intro.

Simple project written in Nest.js which allows to generate invoices.

  1. Here is my controller:
@Controller('pdf')
export class PdfController {

  constructor(private service: PdfService) { }

  @Post('invoice')
  @Header('Content-Type', 'application/pdf')
  async generateInvoice(@Body() invoice: InvoiceDto): Promise<Buffer> {
    return await this.service.generateInvoice(invoice);
  }

}
  1. Here is my service
@Injectable()
export class PdfService {

  async generateInvoice(invoiceData: InvoiceDto): Promise<Buffer> {
    const template = fs.readFileSync(join(__dirname, '..', 'pdf/templates/invoice.hbs'), 'utf-8');
    const compiledTemplate = handlebars.compile(template);
    return await this.generatePDF(compiledTemplate(invoiceData));
  }

  async generatePDF(html: string): Promise<Buffer> {
    try {
      const browser = await puppeteer;
      const page = await browser.newPage();
      await page.setContent(html);
      return await page.pdf({format: 'A4'});
    } catch (err) {
      throw new HttpException(err, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

}

When I post to my endpoint on Postman I have followed response:

Response screenshot

Everything looks like it should work, but when I save this PDF using Postman and tries to open it it says that file is corrupted.

I also tried to fetch it from a simple frontend using this method:

const downloadPDF = () => {
        fetch('/pdf/invoice', {
          body: JSON.stringify(testInvoice),
          method: 'POST'
        }).then(res => {
          return res
            .arrayBuffer()
            .then(res => {
              const blob = new Blob([res], { type: 'application/pdf' })
              console.log(blob)
              anchor.href = window.URL.createObjectURL(blob)
              anchor.style.color = 'green'
            })
            .catch(e => alert(e))
        })
      }

But there is the same problem - it says that the file is corrupted.

IMPORTANT! When I save it in local dir by


return await page.pdf({path: 'invoice.pdf', format: 'A4'});

Then it works correctly...

Im really close to suicide - I've lost hours on that, I will be so grateful for help. Thank you in advance good people!

Upvotes: 1

Views: 2438

Answers (1)

Jakub Wąsowski
Jakub Wąsowski

Reputation: 41

Solution! According to @hardcoded answer.

I refactored my controller:

  @Post('invoice')
  @Header('Content-Type', 'application/pdf')
  async generateInvoice(
    @Body() invoice: InvoiceDto,
    @Res() res: Response,
  ) {

    const buffer = await this.service.generateInvoice(invoice);
    const stream = this.service.getReadableStream(buffer);

    stream.pipe(res);

  }

and then added new method which returns ReadableStream in my PdfService:

  getReadableStream(buffer: Buffer): Readable {

    const stream = new Readable();

    stream.push(buffer);
    stream.push(null);

    return stream;
  }

And that's it! Im recommending also to get into below handbook, it's very clear and complete how to dive deep into streams in Node.js:

https://github.com/substack/stream-handbook

Upvotes: 1

Related Questions