← 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
public/
index.html
about.html
css/
style.css
js/
app.js
images/
logo.png
1. Complete Static File Server
const http = require('http');
const fs = require('fs/promises');
const path = require('path');
const PORT = 3000;
const PUBLIC_DIR = path.join(__dirname, 'public');
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'
};
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
const server = http.createServer(async (req, res) => {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Method Not Allowed');
return;
}
const url = new URL(req.url, `http://${req.headers.host}`);
let pathname = url.pathname;
if (pathname === '/') {
pathname = '/index.html';
}
const safePath = path.normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, '');
const filePath = path.join(PUBLIC_DIR, safePath);
if (!filePath.startsWith(PUBLIC_DIR)) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('Forbidden');
return;
}
try {
const stats = await fs.stat(filePath);
if (stats.isDirectory()) {
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;
}
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'
});
res.end(content);
console.log(`[200] ${req.method} ${pathname} (${mimeType})`);
} catch (err) {
if (err.code === 'ENOENT') {
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 {
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
<!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
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
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';
const stream = fs.createReadStream(filePath);
stream.on('open', () => {
res.writeHead(200, { 'Content-Type': contentType });
stream.pipe(res);
});
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'));
Security Considerations for File Servers:
- Directory traversal: Always sanitize paths. A request for
/../../../etc/passwd must be blocked.
- Restrict to public directory: Only serve files from a designated folder.
- Hidden files: Don't serve
.env, .git, or other hidden files.
- For production: Use Express.js with
express.static() or a reverse proxy like Nginx.
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'));
app.listen(3000);
This is why we use frameworks - they handle all the edge cases for us!
← Back to Examples