typescript 41 lines · 7 steps

Streaming CSV file downloads in NestJS

A NestJS controller streams a report file to the client without buffering it in memory.

Explained by highlit
1import { Controller, Get, Param, StreamableFile, Res, NotFoundException, Header } from '@nestjs/common';
2import { Response } from 'express';
3import { createReadStream, promises as fsp } from 'fs';
4import { join } from 'path';
5import { ReportsService } from './reports.service';
6 
7@Controller('reports')
8export class ReportsController {
9 constructor(private readonly reportsService: ReportsService) {}
10 
11 @Get(':id/export')
12 @Header('Content-Type', 'text/csv')
13 async export(
14 @Param('id') id: string,
15 @Res({ passthrough: true }) res: Response,
16 ): Promise<StreamableFile> {
17 const report = await this.reportsService.findExport(id);
18 if (!report) {
19 throw new NotFoundException(`Report ${id} not found`);
20 }
21 
22 const filePath = join(this.reportsService.exportDir, report.fileName);
23 
24 let stat;
25 try {
26 stat = await fsp.stat(filePath);
27 } catch {
28 throw new NotFoundException('Export file is no longer available');
29 }
30 
31 res.set({
32 'Content-Length': stat.size.toString(),
33 'Content-Disposition': `attachment; filename="${report.fileName}"`,
34 });
35 
36 const stream = createReadStream(filePath);
37 return new StreamableFile(stream, {
38 disposition: `attachment; filename="${report.fileName}"`,
39 });
40 }
41}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1StreamableFile lets you send large files as a stream instead of loading them fully into memory.
  2. 2Using passthrough:true keeps NestJS in control of the response while you still set custom headers manually.
  3. 3Validating both the database record and the file on disk avoids serving broken or missing downloads.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Streaming CSV file downloads in NestJS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code