The HTTP Module Deep Dive
While frameworks like Express simplify HTTP handling, understanding the raw http module
is essential. It teaches you what frameworks abstract away and helps you debug issues at the protocol level.
🌐 HTTP Request Lifecycle
Client Sends Request
→
Server Receives
→
Parse & Route
→
Process
→
Send Response
Handling POST Data and JSON Bodies
Request bodies arrive in chunks that must be collected and parsed:
const http = require('http');
function parseBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => {
const body = Buffer.concat(chunks).toString();
try {
resolve(JSON.parse(body));
} catch {
resolve(body); // Not JSON, return raw string
}
});
req.on('error', reject);
});
}
const server = http.createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/api/users') {
const body = await parseBody(req);
console.log('Received:', body);
// Validate
if (!body.name || !body.email) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Name and email required' }));
}
// Process and respond
const user = { id: Date.now(), ...body };
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
}
});
server.listen(3000);
Building a Router
const http = require('http');
// Simple pattern-matching router
class Router {
constructor() {
this.routes = [];
}
add(method, path, handler) {
// Convert /users/:id to regex
const paramNames = [];
const pattern = path.replace(/:([^/]+)/g, (_, name) => {
paramNames.push(name);
return '([^/]+)';
});
this.routes.push({
method,
regex: new RegExp(`^${pattern}$`),
paramNames,
handler
});
}
get(path, handler) { this.add('GET', path, handler); }
post(path, handler) { this.add('POST', path, handler); }
put(path, handler) { this.add('PUT', path, handler); }
delete(path, handler) { this.add('DELETE', path, handler); }
resolve(method, url) {
for (const route of this.routes) {
if (route.method !== method) continue;
const match = url.match(route.regex);
if (match) {
const params = {};
route.paramNames.forEach((name, i) => params[name] = match[i + 1]);
return { handler: route.handler, params };
}
}
return null;
}
}
// Usage
const router = new Router();
const users = [];
router.get('/api/users', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(users));
});
router.get('/api/users/:id', (req, res, params) => {
const user = users.find(u => u.id === params.id);
if (!user) {
res.writeHead(404, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Not found' }));
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
});
const server = http.createServer((req, res) => {
const { pathname } = new URL(req.url, `http://${req.headers.host}`);
const result = router.resolve(req.method, pathname);
if (result) {
result.handler(req, res, result.params);
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Route not found' }));
}
});
server.listen(3000);
Serving Static Files
const http = require('http');
const fs = require('fs');
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon'
};
function serveStatic(baseDir) {
return (req, res) => {
// Prevent directory traversal attacks
const safePath = path.normalize(req.url).replace(/^(\.\.\/)+/, '');
let filePath = path.join(baseDir, safePath);
// Default to index.html for directories
if (filePath.endsWith('/')) filePath += 'index.html';
const ext = path.extname(filePath);
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', () => {
res.writeHead(404);
res.end('File not found');
});
};
}
const server = http.createServer(serveStatic('./public'));
server.listen(3000);
CORS Headers
const http = require('http');
function setCORSHeaders(res, origin = '*') {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight 24h
}
const server = http.createServer(async (req, res) => {
// Handle CORS for all routes
setCORSHeaders(res, 'https://myapp.com');
// Preflight request
if (req.method === 'OPTIONS') {
res.writeHead(204);
return res.end();
}
// Your API routes here
if (req.url === '/api/data') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'CORS-enabled response' }));
}
});
server.listen(3000);
Query Strings and URL Parsing
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
// Parse query parameters
// URL: /search?q=nodejs&page=2&limit=10
const query = url.searchParams.get('q'); // 'nodejs'
const page = parseInt(url.searchParams.get('page') || '1');
const limit = parseInt(url.searchParams.get('limit') || '20');
console.log({ pathname: url.pathname, query, page, limit });
// Filter and paginate
if (url.pathname === '/api/search') {
const results = database.filter(item =>
item.title.toLowerCase().includes(query.toLowerCase())
);
const paginated = results.slice((page - 1) * limit, page * limit);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
results: paginated,
total: results.length,
page,
totalPages: Math.ceil(results.length / limit)
}));
}
});
server.listen(3000);
💡 Key Takeaways
- • Request bodies arrive in chunks; collect and parse them manually
- • Build a simple router with regex for parameter extraction
- • Stream static files instead of reading them entirely into memory
- • Always handle CORS preflight OPTIONS requests
- • Use the URL constructor for robust URL and query string parsing