NestJS集成Stripe报价PDF下载报错:循环结构转JSON失败
问题描述
我正在将Stripe的发票与报价功能集成到NestJS API中,实现报价PDF下载功能时碰到了Node.js createWriteStream相关问题。
Stripe API提供的示例代码:
const {createWriteStream} = require("fs"); // 返回一个 stream.Readable const pdf = await stripe.quotes .pdf("qt_0J1EnX589O8KAxCGEdmhZY3r"); await new Promise((resolve) => { pdf.pipe(createWriteStream("/tmp/tmp.pdf")); pdf.on("end", () => resolve()); })
我的实现代码:
@Get('/:id/pdf') async downloadQuote(@Param('id') id: string) { const pdf = await this._stripeClient.quotes.pdf(id) await new Promise<void>((resolve) => { pdf.pipe(createWriteStream('/tmp/tmp.pdf')) pdf.on('end', () => resolve()) }) return { success: true, data: pdf } }
运行后出现错误:
TypeError: Converting circular structure to JSON --> starting at object with constructor 'IncomingMessage' | property 'req' -> object with constructor 'ClientRequest' --- property 'res' closes the circle
解决方案
问题核心是你最后返回了pdf这个流对象。NestJS会自动将返回的对象序列化为JSON,但pdf是包含循环引用的可读流(比如IncomingMessage里的req和res互相引用),无法直接转成JSON。
另外你的逻辑存在冗余:Stripe返回的PDF流不需要先写入本地文件再返回,直接将流pipe到响应对象即可,前端能直接触发PDF下载,省去本地文件IO的开销。
推荐实现方式
import { Response } from '@nestjs/common'; @Get('/:id/pdf') async downloadQuote(@Param('id') id: string, @Res() res: Response) { const pdf = await this._stripeClient.quotes.pdf(id); // 设置响应头,让浏览器识别这是可下载的PDF文件 res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="quote.pdf"'); // 直接将PDF流导向响应对象 pdf.pipe(res); }
若需先写入本地文件再返回(不推荐)
如果业务必须先保存本地文件,写完后要返回文件内容而非原流对象:
import { createReadStream } from 'fs'; import { Response } from '@nestjs/common'; @Get('/:id/pdf') async downloadQuote(@Param('id') id: string, @Res() res: Response) { const pdf = await this._stripeClient.quotes.pdf(id); const filePath = '/tmp/tmp.pdf'; await new Promise<void>((resolve) => { pdf.pipe(createWriteStream(filePath)).on('finish', resolve); }); // 将本地PDF文件返回给前端下载 res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="quote.pdf"'); createReadStream(filePath).pipe(res); }
关键注意点
- 绝对不要返回Stripe的原始流对象给前端,NestJS无法序列化带循环引用的流结构
- 直接pipe流到响应对象是最高效的实现方式,避免不必要的本地文件操作
- 必须设置正确的
Content-Type和Content-Disposition响应头,确保浏览器正确处理PDF文件
内容的提问来源于stack exchange,提问作者Jason McFarlane
相关产品推荐
相关产品推荐

