How do you send a large file to the browser without loading it all in memory?
Create a read stream of the file and pipe it into the HTTP response. Set the Content-Type and, if known, Content-Length headers first, and handle stream errors before any bytes are sent.
Explain it simply
Using readFile and res.send for a 500 MB video forces the server to hold the whole file in RAM for every viewer. With a stream the server handles it like a water pipe: a little at a time goes from disk to the network. Ten viewers cost ten small buffers, not ten copies of the file.
Example
const express = require("express");
const fs = require("fs");
const path = require("path");
const { pipeline } = require("stream");
const app = express();
app.get("/lectures/:name", (req, res, next) => {
const file = path.join(__dirname, "videos", path.basename(req.params.name));
fs.stat(file, (err, stats) => {
if (err) return res.status(404).send("Not found");
res.setHeader("Content-Type", "video/mp4");
res.setHeader("Content-Length", stats.size);
pipeline(fs.createReadStream(file), res, (error) => {
if (error) console.error("stream failed", error.message);
});
});
});
app.listen(3000);Details:
path.basenamestops a request like../../secretfrom escaping the folder.pipelinedestroys the file stream if the user closes the tab, so the descriptor is freed.- For video seeking, support the
Rangeheader;res.sendFiledoes this for you.
What to say in the interview
"I pipe fs.createReadStream into res with pipeline, set content headers up front, and sanitise the filename. That keeps memory flat and cleans up if the client disconnects. res.sendFile is the simpler option for ordinary files."
Common mistakes
- Using
readFilefor big downloads. - Joining user input into the path without sanitising it.