iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…