NestJS Generate PDF with PDFKit

Kevin Firko
Published:

This guide covers using PDFKit in a NestJS API to generate a PDF and send it back to a client.

There are a ton of libraries within the JavaScript/TypeScript ecosystem to generate PDF files.

I chose PDFKit because it’s popular and it doesn’t require high-overhead dependencies such as a headless web browser. You can substitute a different library if you prefer.

This guide assumes that you’re using the default Express configuration for NestJS rather than Fastify.

Install Dependencies

The following examples use yarn. You are free to use your favourite package manager such as npm.

yarn add pdfkit
yarn add --dev @types/pdfkit

NestJS Service

Suppose you have a NestJS service that’s decorated with the Injectable() decorator.

Import PDFDocument from pdfkit:

import * as PDFDocument from 'pdfkit'

Next, create a function within your service to generate your PDF.

The following example generates a basic “Hello World” PDF. The function wraps the PDF production step in a Promise that resolves with a Buffer containing the PDF data:

  async generatePDF(): Promise<Buffer> {
    const pdfBuffer: Buffer = await new Promise(resolve => {
      const doc = new PDFDocument({
        size: 'LETTER',
        bufferPages: true,
      })

      // customize your PDF document
      doc.text('hello world', 100, 50)
      doc.end()

      const buffer = []
      doc.on('data', buffer.push.bind(buffer))
      doc.on('end', () => {
        const data = Buffer.concat(buffer)
        resolve(data)
      })
    })

    return pdfBuffer
  }

There are a ton of options that you can pass to PDFDocument(), and a ton of ways that you can customize the PDF beyond the text() method shown above.

Refer to the PDFKit Docs for all the details.

NestJS Controller

Ensure that your controller’s constructor references your service so that it is made available via NestJS’ Dependency Injection:

// ...
  constructor(
    private exampleService: ExampleService
  ){}
// ...

Next, implement a function to download a generated PDF.

The following example uses the @Res() decorator (imported from @nestjs/common) to access the underlying ExpressJS Response object (Response is imported from express):

  @Get('/pdf')
  async getPDF(
    @Res() res: Response,
  ): Promise<void> {
    const buffer = await this.exampleService.generatePDF()

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': 'attachment; filename=example.pdf',
      'Content-Length': buffer.length,
    })

    res.end(buffer)
  }

Rather than use NestJS’ Header() decorator, we set the headers manually on the response object so that we can specify the Content-Length.

The generated PDF is sent back to the client by passing the buffer to res.end().

If you want the client’s browser to open the PDF by default rather than download it, change the attachment keyword in the Content-Disposition header to inline.

If you’re using a different PDF library that returns the PDF as a stream, you can return it to the client via stream.pipe(res).

Don’t forget to add cache-busting headers such as Cache-Control if you want client browsers to always fetch the latest version of your PDF file given repeated requests.

Let me know in the comments if this guide was helpful to you!