How to Stream Large File Downloads in Node.js Without Memory Issues
Stream large files in Node.js using fs.createReadStream and Express without loading into memory. Covers backpressure, range request support, and CDN integration.
Serving large files directly from Node.js without a CDN or object storage proxy requires streaming. Loading a 1GB file into memory with fs.readFileSync consumes 1GB of server RAM per connected user—rapidly leading to process termination.
Streaming Files with Node.js HTTP Streams
Node.js readable streams buffer only small chunks (typically 64KB) in memory at any given moment:
import http from 'http';
import fs from 'fs';
import path from 'path';
const server = http.createServer((req, res) => {
const filePath = path.join(process.cwd(), 'files', 'large-dataset.pdf');
fs.stat(filePath, (err, stats) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('File not found');
}
// Set binary response headers
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Length': stats.size,
'Content-Disposition': 'attachment; filename="large-dataset.pdf"',
'Accept-Ranges': 'bytes',
});
const readStream = fs.createReadStream(filePath);
// Automatically handles TCP backpressure
readStream.pipe(res);
// Handle client disconnects
req.on('close', () => {
readStream.destroy();
});
});
});
server.listen(3000, () => console.log('Download server running on port 3000'));
Download our 500MB PDF sample file to test stream backpressure handling in your server code.
Download 500MB PDF Sample →Understanding Stream Backpressure
When a server can read from disk faster than a client can receive data over the network, stream.pipe(res) automatically pauses the read stream until the client's socket drain buffer clears, preserving server memory.
Nguyen Dai Long
AuthorBackend Lead • Distributed Systems & Cloud Edge Architecture Specialist
4+ years designing high-throughput file ingestion pipelines, database architectures, and distributed edge storage on Cloudflare R2 & AWS S3. Founder of FileDummy and the NDL Ecosystem.
Was this article helpful?
Click Like to support the author and help other developers discover this guide.
Engineering Discussion & Feedback (0)
Share benchmark results, report edge cases, or ask technical questions.
No comments yet. Be the first developer to start the discussion!
Frequently Asked Questions
Why does Node.js run out of memory when serving large files?â–¾
Using fs.readFileSync() or res.send(buffer) loads the entire file into heap memory. For a 1GB file with 10 concurrent downloads, that is 10GB of RAM. Use fs.createReadStream().pipe(res) instead — it reads in 64KB chunks, keeping memory constant regardless of file size.
What is backpressure in Node.js streams?â–¾
Backpressure occurs when the writable stream (HTTP response) cannot consume data as fast as the readable stream (file read) produces it. Using .pipe() handles backpressure automatically by pausing the file read when the response write buffer is full.
How do I add HTTP Range request support to my Node.js file server?â–¾
Parse the Range header, calculate the byte range, use fs.createReadStream(path, { start, end }), and respond with 206 Partial Content and the Content-Range header. This enables browser resume support and video seeking.