NestJS: return PDF file from Buffer using StreamableFile
Asked Answered
K

1

9

I'm using NestJS 8.0.2 and I'm trying to return a PDF from an API endpoint using the new StreamableFile class which, as the documentation says:

A StreamableFile is a class that holds onto the stream that is to be returned. To create a new StreamableFile, you can pass either a Buffer or a Stream to the StreamableFile constructor.

I'm using a PDF printing library called pdfMake, which provides an option to return the PDF from memory as a Buffer object. I'm trying to avoid saving the generated PDF to the filesystem if possible and just return it directly.

So I'm trying to combine the two by doing the following:

pdf-service.ts

async generatePDF(inputs: PDFInputsDTO) {
  try {
    const definition = this.generateDocDefinition(inputs);
    const document = pdfMake.createPdf(definition);
    const promise = new Promise((resolve, reject) => {
      try {
        document.getBuffer((result) => {
          resolve(result); // result is of type Buffer
      } catch (e) {
        reject(e);
      }
    });

    return promise as Promise<Buffer>;
  } catch (e) {
    throw new InternalServiceException('PDF generation error');
  }
}

pdf-controller.ts

@Post('pdf')
@HttpCode(200)
@UseFilters(ValidationExceptionFilter)
@UsePipes(new ValidationPipe(PdfController.pipeOptions))
@Header('Content-Type', 'application/pdf')
@Header('Content-Disposition', 'inline; filename=file.pdf')
async generatePDFFile(@Body() inputs: PDFInputsDTO) {
  try {
    const pdfFile = await this.pdfService.generatePDF(inputs);

    console.log(`PDF is: ${pdfFile}`); // prints "PDF is: <Buffer 25 50 44 46 2d 31 2e 33 0a 25 ff ff ff ff 0a 39 20 30 20 6f 62 6a 0a 3c 3c 0a 2f 54 79 70 65 20 2f 45 78 74 47 53 74 61 74 65 0a 2f 63 61 20 31 0a 2f ... >"

    return new StreamableFile(pdfFile);
  } catch(e) {
    throw(e);
  }
}

This results in an exception:

[Nest] 26539  - 07/24/2021, 4:14:33 PM   ERROR [ExceptionsHandler] Cannot read property 'pipe' of undefined
TypeError: Cannot read property 'pipe' of undefined
    at ExpressAdapter.reply (/Users/agentlogic/Work/Web/Backend/pdf-generator/node_modules/@nestjs/platform-express/adapters/express-adapter.js:27:36)
    at RouterResponseController.apply (/Users/agentlogic/Work/Web/Backend/pdf-generator/node_modules/@nestjs/core/router/router-response-controller.js:14:36)
    at /Users/agentlogic/Work/Web/Backend/pdf-generator/node_modules/@nestjs/core/router/router-execution-context.js:175:48
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at /Users/agentlogic/Work/Web/Backend/pdf-generator/node_modules/@nestjs/core/router/router-execution-context.js:47:13
    at /Users/agentlogic/Work/Web/Backend/pdf-generator/node_modules/@nestjs/core/router/router-proxy.js:9:17

ADDITIONAL INFO

Using the Response class from express works, but I want to be able to respond as JSON if there are any input validation errors (using UseFilters and UsePipes):

async generatePDFFile(@Body() inputs: PDFInputsDTO, @Res() response: Response) {
  try {
    const pdfFile = await this.pdfService.generatePDF(inputs);

    response.set({ 'Content-Length': pdfFile.length });
    response.end(pdfFile);
  } catch (e)
    throw e;
  }
}

The above works, but I lose the ability to automatically respond with any input validation errors as as JSON object.

Katharinakatharine answered 24/7, 2021 at 11:3 Comment(6)
do console.log(Buffer.isBuffer(pdfFile)) prints true?Flavone
I think that if you use return printer.createPdfKitDocument(definition) (as shown here) in your generatePDF, it will work.Flavone
@MicaelLevi Oh, console.log(Buffer.isBuffer(pdfFile)) prints false... Your helpful comment led me to do an additional const buffer = Buffer.from(pdfFile) and return that, and it now responds with Content-Type: application/octet-stream instead of application/pdf...Katharinakatharine
Re: the github link, I'm not sure how to import pdfPrinter from the build file. The example imports it from the src directory...Katharinakatharine
Re: my Content-Type issue above, this seems to be a NestJS issue. Thank you @MicaelLevi for pointing me in the right direction with regards to the Buffer.isBuffer()!Katharinakatharine
PdfPrinter is imported from pdfmake. I just tested it. Its latest published versionFlavone
B
0

Something like this worked for me. Basically the await buffer(...) does the job of transforming the stream into a buffer that StreamableFile understands.

import { buffer } from 'node:stream/consumers';

async generatePDFFile(@Body() inputs: PDFInputsDTO) {
  const pdfFile = await this.pdfService.generatePDF(inputs);
  return new StreamableFile(await buffer(pdfFile), {
    disposition: 'inline; filename=asd.pdf',
    type: 'application/pdf; charset=utf-8',
  });
}
Blent answered 7/8, 2024 at 14:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.