Bare HTTP Server
Node ships an http module that can serve real production traffic without Express, Fastify, or any framework. Reach for it when you want zero dependencies, a tiny container, or when learning what a framework does for you. The shape: createServer with a request listener, parse the URL and method yourself, and stream the response.
A zero-dep HTTP server with routing and JSON
EXAMPLE
const http = require('node:http');
const { URL } = require('node:url');
const { randomUUID } = require('node:crypto');
const orders = new Map(); // in-memory store for the demo
// Tiny router: array of { method, pattern, handler }
const routes = [];
const route = (method, pattern, handler) => routes.push({ method, pattern, handler });
route('GET', /^\/orders\/(?<id>[\w-]+)$/, (req, res, m) => {
const o = orders.get(m.groups.id);
if (!o) return json(res, 404, { error: 'not found' });
json(res, 200, o);
});
route('GET', /^\/orders$/, (req, res) => {
json(res, 200, [...orders.values()]);
});
route('POST', /^\/orders$/, async (req, res) => {
const body = await readJson(req, 64 * 1024); // 64 KB cap
if (!body || typeof body.customer !== 'string') {
return json(res, 400, { error: 'customer required' });
}
const o = { id: randomUUID(), ...body, status: 'new', created_at: new Date() };
orders.set(o.id, o);
res.setHeader('Location', \`/orders/${o.id}\`);
json(res, 201, o);
});
route('DELETE', /^\/orders\/(?<id>[\w-]+)$/, (req, res, m) => {
json(res, orders.delete(m.groups.id) ? 204 : 404, null);
});
// Helpers
function json(res, status, body) {
res.statusCode = status;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(body == null ? '' : JSON.stringify(body));
}
function readJson(req, maxBytes) {
return new Promise((resolve) => {
let size = 0;
const chunks = [];
req.on('data', (c) => {
size += c.length;
if (size > maxBytes) { req.destroy(); resolve(null); return; }
chunks.push(c);
});
req.on('end', () => {
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || 'null')); }
catch { resolve(null); }
});
req.on('error', () => resolve(null));
});
}
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url, 'http://x'); // base required by URL
for (const r of routes) {
if (r.method !== req.method) continue;
const m = url.pathname.match(r.pattern);
if (m) return r.handler(req, res, m, url);
}
json(res, 404, { error: 'not found' });
} catch (err) {
console.error(err);
json(res, 500, { error: 'internal' });
}
});
// Graceful shutdown — close keep-alive sockets, finish in-flight requests
function shutdown() {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
server.listen(3000, () => console.log('listening :3000'));
Why it matters
A zero-dep server like this is a great rubber-duck for understanding what frameworks add: route compilation, body parsing limits, content-type negotiation, async error capture, helmet headers, etc. For real production work pick Fastify or Express — but you will write better code in them after reading this 60-line file.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import http from 'node:http';
http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ ok: true }));
}).listen(3000);
Try it Yourself »
Discussion
Loading…