Stream Files From Disk to the Response
Serving Static Files
Serve files from disk safely — with the right Content-Type and no directory-traversal bugs.
What you'll learn
- Stream a file to res
- Set Content-Type from extension
- Avoid path-traversal
Serving a file from disk over HTTP — common, easy to get wrong.
The Naive Version
import { createServer } from "node:http";
import { createReadStream } from "node:fs";
import { join } from "node:path";
createServer((req, res) => {
const path = join("./public", req.url); // ⚠️ DANGEROUS
createReadStream(path).pipe(res);
}).listen(3000); Two problems:
- Path traversal:
GET /../etc/passwdreads anywhere on disk. - No Content-Type: browser guesses, sometimes wrong.
The Safer Version
import { createServer } from "node:http";
import { createReadStream } from "node:fs";
import { stat } from "node:fs/promises";
import { join, normalize, extname } from "node:path";
const ROOT = "./public";
const MIME = {
".html": "text/html; charset=utf-8",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".svg": "image/svg+xml",
};
createServer(async (req, res) => {
// 1. Normalize and validate path
const requested = decodeURIComponent(req.url.split("?")[0]);
const safePath = normalize(requested).replace(/^[\/\\]+/, "");
if (safePath.includes("..")) {
res.statusCode = 403;
return res.end("forbidden");
}
const path = join(ROOT, safePath || "index.html");
// 2. Make sure it exists and is a file
try {
const s = await stat(path);
if (!s.isFile()) throw new Error("not a file");
} catch {
res.statusCode = 404;
return res.end("not found");
}
// 3. Send with content type
res.setHeader("content-type", MIME[extname(path)] ?? "application/octet-stream");
createReadStream(path).pipe(res);
}).listen(3000); The pattern: normalize → reject .. → stat → pipe.
In Real Code
You won’t write this. Use:
- Express —
express.static("./public") - Fastify —
@fastify/static - Nginx / Caddy — let the reverse proxy serve static files (faster, with caching)
For production: serve static files from a CDN or the edge. Node should serve dynamic content only.
Cookies →