I try to return a PDF file from a Controller Endpoint using NestJs. When not setting the Content-type
header, the data returned by getDocumentFile
gets returned to the user just fine. When I add the header however, the return I get seems to be some strange form of a GUID, the response always looks like this: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
where x
is a lowercase hexadecimal character. It also seems to be totally unrelated to the actual return value of the handler function, as I even get this strange GUID-thing when not returning anything at all.
When not setting Content-type: application/pdf
, the function returns the data of the buffer just fine, however I need to set the header in order to get the browser to recognize the response as a PDF file which is important for my use case.
The controller looks like this:
@Controller('documents')
export class DocumentsController {
constructor(private documentsService: DocumentsService) {}
@Get(':id/file')
@Header('Content-type', 'application/pdf')
async getDocumentFile(@Param('id') id: string): Promise<Buffer> {
const document = await this.documentsService.byId(id)
const pdf = await this.documentsService.getFile(document)
// using ReadableStreamBuffer as suggested by contributor
const stream = new ReadableStreamBuffer({
frequency: 10,
chunkSize: 2048,
})
stream.put(pdf)
return stream
}
}
and my DocumentsService like this:
@Injectable()
export class DocumentsService {
async getAll(): Promise<Array<DocumentDocument>> {
return DocumentModel.find({})
}
async byId(id: string): Promise<DocumentDocument> {
return DocumentModel.findOne({ _id: id })
}
async getFile(document: DocumentDocument): Promise<Buffer> {
const filename = document.filename
const filepath = path.join(__dirname, '..', '..', '..', '..', '..', 'pdf-generator', 'dist', filename)
const pdf = await new Promise<Buffer>((resolve, reject) => {
fs.readFile(filepath, {}, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
return pdf
}
}
I originally just returned the buffer (return pdf
), but that brought the same result as the attempt above. On the repository of NestJs a user suggested to use the above method, which obviously does not work for me either. See the GitHub thread here.
Updated in 2021 too:
I prefer this way as I don't need the post-controller interceptor logic. We can control the name of the file and make it be inline or downloading the file.
@Get()
download(@Res() res) {
const filename = '123.pdf';
// make it to be inline other than downloading
// res.setHeader('Content-disposition', 'inline; filename=' + filename);
res.setHeader('Content-disposition', 'attachment; filename=' + filename);
const filestream = createReadStream('files/' + filename);
filestream.pipe(res);
}
More info in the offical Nest Docs: https://docs.nestjs.com/techniques/streaming-files
Update 2021:
From now on in Nest Version 8 you can use the class StreamableFile
:
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';
@Controller('file')
export class FileController {
@Get()
getFile(): StreamableFile {
const file = createReadStream(join(process.cwd(), 'package.json'));
return new StreamableFile(file);
}
}
More info in the offical Nest Docs: https://docs.nestjs.com/techniques/streaming-files
You can just use ready decorator @Res this is my working solution:
Controller(NestJs):
async getNewsPdfById(@Param() getNewsParams: GetNewsPdfParams, @Req() request: Request, @Res() response: Response): Promise<void> {
const stream = await this.newsService.getNewsPdfById(getNewsParams.newsId, request.user.ownerId);
response.set({
'Content-Type': 'image/pdf',
});
stream.pipe(response);
}
In my case stream variable is just ready stream created by html-pdf library because i create pdf by html https://www.npmjs.com/package/html-pdf but it doesnt matter how you create your stream. The thing is that you should use @Res decorator and pipe it because its native NestJs solution.
Also here is code how to claim file on client side: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
Anyway lets try this one in your case:
@Controller('documents')
export class DocumentsController {
constructor(private documentsService: DocumentsService) {}
@Get(':id/file')
async getDocumentFile(@Param('id') id: string, @Res res: Response): Promise<Buffer> {
const document = await this.documentsService.byId(id)
const pdf = await this.documentsService.getFile(document)
const stream = new ReadableStreamBuffer({
frequency: 10,
chunkSize: 2048,
})
res.set({
'Content-Type': 'image/pdf',
});
stream.pipe(res);
}
}
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