Serving Static Files & HTTPS

📖 Concept

Serving static files (HTML, CSS, JS, images) and enabling HTTPS are fundamental skills for building production web servers.

Static file serving: The raw http module doesn't have built-in static file serving — you must implement it manually by reading files and setting appropriate MIME types.

MIME types matter: Browsers use the Content-Type header to determine how to handle a response. Sending an HTML file with text/plain renders it as text, not a page.

Extension MIME Type
.html text/html
.css text/css
.js application/javascript
.json application/json
.png image/png
.jpg image/jpeg
.svg image/svg+xml
.pdf application/pdf
.woff2 font/woff2

HTTPS: Production servers require HTTPS. Node.js provides the https module which works identically to http but requires an SSL/TLS certificate.

Security headers: Every production server should include security headers:

  • Content-Security-Policy — Prevent XSS attacks
  • X-Content-Type-Options: nosniff — Prevent MIME-type sniffing
  • X-Frame-Options: DENY — Prevent clickjacking
  • Strict-Transport-Security — Enforce HTTPS

🏠 Real-world analogy: Serving static files is like a library (the building). Books (files) are organized on shelves (directories), and each book has a classification (MIME type). HTTPS is like having a locked door and security guard — visitors can still read the books, but the communication is private.

💻 Code Example

codeTap to expand ⛶
1// Static File Server & HTTPS
2
3const http = require("http");
4const https = require("https");
5const fs = require("fs");
6const path = require("path");
7
8// 1. MIME type mapping
9const MIME_TYPES = {
10 ".html": "text/html; charset=utf-8",
11 ".css": "text/css",
12 ".js": "application/javascript",
13 ".json": "application/json",
14 ".png": "image/png",
15 ".jpg": "image/jpeg",
16 ".jpeg": "image/jpeg",
17 ".gif": "image/gif",
18 ".svg": "image/svg+xml",
19 ".ico": "image/x-icon",
20 ".pdf": "application/pdf",
21 ".woff": "font/woff",
22 ".woff2": "font/woff2",
23 ".ttf": "font/ttf",
24 ".mp4": "video/mp4",
25 ".webp": "image/webp",
26};
27
28// 2. Static file server with security
29function createStaticServer(publicDir) {
30 return http.createServer(async (req, res) => {
31 // Security headers
32 res.setHeader("X-Content-Type-Options", "nosniff");
33 res.setHeader("X-Frame-Options", "DENY");
34 res.setHeader("X-XSS-Protection", "1; mode=block");
35
36 // Only serve GET/HEAD requests
37 if (req.method !== "GET" && req.method !== "HEAD") {
38 res.writeHead(405, { Allow: "GET, HEAD" });
39 res.end("Method Not Allowed");
40 return;
41 }
42
43 // Parse URL and prevent directory traversal
44 let filePath = path.join(publicDir, decodeURIComponent(req.url));
45
46 // ✅ Security: Prevent path traversal attacks
47 if (!filePath.startsWith(publicDir)) {
48 res.writeHead(403);
49 res.end("Forbidden");
50 return;
51 }
52
53 try {
54 const stats = await fs.promises.stat(filePath);
55
56 // Serve index.html for directories
57 if (stats.isDirectory()) {
58 filePath = path.join(filePath, "index.html");
59 }
60
61 // Get MIME type
62 const ext = path.extname(filePath).toLowerCase();
63 const contentType = MIME_TYPES[ext] || "application/octet-stream";
64
65 // Caching headers
66 const isAsset = [".css", ".js", ".png", ".jpg", ".woff2"].includes(ext);
67 if (isAsset) {
68 res.setHeader("Cache-Control", "public, max-age=31536000"); // 1 year
69 } else {
70 res.setHeader("Cache-Control", "no-cache");
71 }
72
73 // Stream the file (memory efficient for large files)
74 res.writeHead(200, { "Content-Type": contentType });
75
76 if (req.method === "HEAD") {
77 res.end();
78 return;
79 }
80
81 const stream = fs.createReadStream(filePath);
82 stream.pipe(res);
83 stream.on("error", () => {
84 res.writeHead(500);
85 res.end("Internal Server Error");
86 });
87 } catch (err) {
88 if (err.code === "ENOENT") {
89 res.writeHead(404, { "Content-Type": "text/html" });
90 res.end("<h1>404 — Not Found</h1>");
91 } else {
92 res.writeHead(500);
93 res.end("Internal Server Error");
94 }
95 }
96 });
97}
98
99// 3. HTTPS server
100function createHTTPSServer() {
101 const options = {
102 key: fs.readFileSync("certs/private-key.pem"),
103 cert: fs.readFileSync("certs/certificate.pem"),
104 // For production, also include CA chain:
105 // ca: fs.readFileSync("certs/ca-chain.pem"),
106 };
107
108 const server = https.createServer(options, (req, res) => {
109 res.writeHead(200, { "Content-Type": "text/plain" });
110 res.end("Secure connection established!");
111 });
112
113 server.listen(443, () => {
114 console.log("HTTPS server running on port 443");
115 });
116}
117
118// 4. HTTP → HTTPS redirect
119function createRedirectServer() {
120 http
121 .createServer((req, res) => {
122 res.writeHead(301, {
123 Location: `https://${req.headers.host}${req.url}`,
124 });
125 res.end();
126 })
127 .listen(80, () => {
128 console.log("HTTP redirect server on port 80");
129 });
130}
131
132// 5. Start static server
133const PUBLIC_DIR = path.resolve(__dirname, "public");
134const server = createStaticServer(PUBLIC_DIR);
135server.listen(3000, () => {
136 console.log(`Static server: http://localhost:3000 (serving ${PUBLIC_DIR})`);
137});

🏋️ Practice Exercise

Exercises:

  1. Build a static file server that serves files from a public/ directory with proper MIME types
  2. Add path traversal protection — ensure requests can't escape the public directory
  3. Implement ETag-based caching for static assets
  4. Create self-signed SSL certificates and start an HTTPS server
  5. Build an HTTP → HTTPS redirect server
  6. Add gzip compression for text-based responses (HTML, CSS, JS, JSON)

⚠️ Common Mistakes

  • Not preventing path traversal attacks — ../../etc/passwd in the URL can expose system files; always validate that the resolved path is within the public directory

  • Using wrong or missing MIME types — serving JavaScript with text/plain causes browsers to reject it; always map file extensions to correct content types

  • Loading entire files into memory before sending — use createReadStream().pipe(res) for efficient streaming, especially for large files

  • Not implementing HTTPS in production — all modern applications require HTTPS; use Let's Encrypt for free certificates

  • Serving user-uploaded files from the same origin — this enables XSS attacks; use a separate domain or CDN for user content

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Serving Static Files & HTTPS. Login to unlock this feature.