← Back to Examples

Static File Server

Build a complete static file server that serves HTML, CSS, JavaScript, and image files.

Project Structure

static-server/ server.js # The server code public/ # Files to serve index.html about.html css/ style.css js/ app.js images/ logo.png

1. Complete Static File Server

// server.js - Complete Static File Server const http = require('http'); const fs = require('fs/promises'); const path = require('path'); // Configuration const PORT = 3000; const PUBLIC_DIR = path.join(__dirname, 'public'); // MIME type mapping const MIME_TYPES = { '.html': 'text/html', '.htm': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.webp': 'image/webp', '.woff': 'font/woff', '.woff2':'font/woff2', '.ttf': 'font/ttf', '.pdf': 'application/pdf', '.zip': 'application/zip', '.txt': 'text/plain', '.xml': 'application/xml', '.mp3': 'audio/mpeg', '.mp4': 'video/mp4' }; // Get MIME type from file extension function getMimeType(filePath) { const ext = path.extname(filePath).toLowerCase(); return MIME_TYPES[ext] || 'application/octet-stream'; } // Create the server const server = http.createServer(async (req, res) => { // Only handle GET requests if (req.method !== 'GET') { res.writeHead(405, { 'Content-Type': 'text/plain' }); res.end('Method Not Allowed'); return; } // Parse the URL const url = new URL(req.url, `http://${req.headers.host}`); let pathname = url.pathname; // Default to index.html for root if (pathname === '/') { pathname = '/index.html'; } // Security: Prevent directory traversal attacks const safePath = path.normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, ''); const filePath = path.join(PUBLIC_DIR, safePath); // Make sure the path is within PUBLIC_DIR if (!filePath.startsWith(PUBLIC_DIR)) { res.writeHead(403, { 'Content-Type': 'text/plain' }); res.end('Forbidden'); return; } try { // Check if file exists and is a file (not directory) const stats = await fs.stat(filePath); if (stats.isDirectory()) { // Try to serve index.html from directory const indexPath = path.join(filePath, 'index.html'); try { const content = await fs.readFile(indexPath); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); } catch { res.writeHead(403, { 'Content-Type': 'text/plain' }); res.end('Directory listing not allowed'); } return; } // Read and serve the file const content = await fs.readFile(filePath); const mimeType = getMimeType(filePath); res.writeHead(200, { 'Content-Type': mimeType, 'Content-Length': stats.size, 'Cache-Control': 'public, max-age=3600' // 1 hour cache }); res.end(content); console.log(`[200] ${req.method} ${pathname} (${mimeType})`); } catch (err) { if (err.code === 'ENOENT') { // File not found - serve 404 page console.log(`[404] ${req.method} ${pathname}`); res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(` <!DOCTYPE html> <html> <head><title>404 - Not Found</title> <style> body { font-family: sans-serif; text-align: center; padding: 50px; } h1 { color: #f44336; font-size: 3rem; } </style> </head> <body> <h1>404</h1> <p>The file "${pathname}" was not found.</p> <a href="/">Go Home</a> </body> </html> `); } else { // Server error console.error(`[500] ${err.message}`); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); } } }); server.listen(PORT, () => { console.log(`Static file server running at http://localhost:${PORT}`); console.log(`Serving files from: ${PUBLIC_DIR}`); console.log('Press Ctrl+C to stop'); });

2. Sample public/index.html

<!-- public/index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Static Site</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <header> <h1>Welcome to My Site</h1> <nav> <a href="/">Home</a> <a href="/about.html">About</a> </nav> </header> <main> <p>This is served by our Node.js static file server!</p> </main> <script src="/js/app.js"></script> </body> </html>

3. Sample public/css/style.css

/* public/css/style.css */ body { font-family: 'Segoe UI', sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; } header { background: #2196f3; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; } nav a { color: white; margin-right: 15px; } main { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }

4. Streaming Large Files

// For large files, use streams instead of reading everything into memory const http = require('http'); const fs = require('fs'); const path = require('path'); const server = http.createServer((req, res) => { const filePath = path.join(__dirname, 'public', req.url === '/' ? 'index.html' : req.url); const ext = path.extname(filePath).toLowerCase(); const MIME_TYPES = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.mp4': 'video/mp4' }; const contentType = MIME_TYPES[ext] || 'application/octet-stream'; // Use createReadStream for efficient memory usage const stream = fs.createReadStream(filePath); stream.on('open', () => { res.writeHead(200, { 'Content-Type': contentType }); stream.pipe(res); // Pipe file data to response }); stream.on('error', (err) => { if (err.code === 'ENOENT') { res.writeHead(404); res.end('File not found'); } else { res.writeHead(500); res.end('Server error'); } }); }); server.listen(3000, () => console.log('Stream server on :3000')); // Why streams? // Reading a 1GB video file: // fs.readFile → 1GB in memory (bad!) // fs.createReadStream → ~64KB chunks (good!) // The data flows from disk to network without // loading the entire file into memory.
Security Considerations for File Servers:

Quick Comparison

Our static server (this example): ~80 lines of code, handles basic file serving.

Express.js equivalent:
const express = require('express'); const app = express(); app.use(express.static('public')); // That's it! One line. app.listen(3000);
This is why we use frameworks - they handle all the edge cases for us!

← Back to Examples