I'm calling an external API that returns a PDF file and I want to return this PDF file in my controller function response.
In My controller class:
@Get(':id/pdf')
async findPdf(@Param('id') id: string, @Res() res: Response) {
const response = await this.documentsService.findPdf(id);
console.log(response.data);
// this prints the following:
// %PDF-1.5
// %����
// 2 0 obj
// << /Type /XObject /Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter // /DCTDecode /Width 626 /Height
// 76 /Length 14780>>
// stream
// and go on...
return res
.status(200)
.header('Content-Type', 'application/pdf')
.header('Content-Disposition', response.headers['content-disposition'])
.send(response.data);
}
In My service class:
findPdf(id: string): Promise<any> {
return firstValueFrom(
this.httpService
.get(`/docs/${id}/pdf`)
.pipe(map((response) => response))
.pipe(
catchError((e) => {
throw new BadRequestException('Failed to get PDF.');
}),
),
);
}
However I'm getting a blank PDF file in my response.
The internal API call works fine, I've tested it from Postman and the PDF file is correct.
What am I doing wrong?
I have tested and reproduced the issue you are describing.
The reason is that your external server responds with PDF as a stream and your solution is not handling it.
First, since the response is the stream, you need to inform Axios about that (HTTP service) by changing:
.get(`/docs/${id}/pdf`)
to:
.get(`/docs/${id}/pdf`, { responseType: "stream" })
After that, you have two approaches (depending on what you need):
You can pipe that stream to your main response (hence deliver to stream to the caller of your service).
You can collect whole stream data from the document server and then deliver the final buffer data to the caller.
I hope this helps.
Complete source code with example is here:
import { HttpService } from "@nestjs/axios";
import { BadRequestException, Controller, Get, Res } from "@nestjs/common";
import { catchError, firstValueFrom, map, Observable } from "rxjs";
import { createReadStream } from "fs";
@Controller('pdf-from-external-url')
export class PdfFromExternalUrlController {
constructor(
private httpService: HttpService
) {
}
// Simulated external document server that responds as stream!
@Get()
async getPDF(@Res() res) {
const file = createReadStream(process.cwd() + '/files/test.pdf');
return file.pipe(res);
}
@Get('indirect-pdf')
async findPdf(@Res() res) {
const pdfResponse = await firstValueFrom(this.httpService
.get(`http://localhost:3000/pdf-from-external-url`, { responseType: "stream" })
.pipe(map((response) => response))
.pipe(
catchError((e) => {
throw new BadRequestException('Failed to get PDF.');
}),
));
// APPROACH (1) - deliver your PDF as stream to your caller
// pdfResponse.data.pipe(res);
// END OF APPROACH (1)
// APPROACH (2) - read whole stream content on server and then deliver it
const streamReadPromise = new Promise<Buffer>((resolve) => {
const chunks = [];
pdfResponse.data.on('data', chunk => {
chunks.push(Buffer.from(chunk));
});
pdfResponse.data.on('end', () => {
resolve(Buffer.concat(chunks));
});
});
const pdfData = await streamReadPromise;
res.header('Content-Type', 'application/pdf')
res.send(pdfData);
// END OF APPROACH (2)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With