The built-in node:http module lets Node.js listen on a TCP port, receive HTTP requests, and send HTML or JSON responses without external frameworks. Learning http.createServer() clarifies how Express, Fastify, and Next.js handle headers, status codes, and request streams under the hood.
Every incoming request invokes your (req, res) listener with an IncomingMessage stream (req) and a writable ServerResponse (res). Always set Content-Type headers explicitly and finish every branch with res.end().
import http from 'node:http';
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
if (req.method === 'GET' && url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'healthy', uptime: process.uptime() }));
return;
}
if (req.method === 'GET' && url.pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Welcome to the Node.js HTTP server.');
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Route not found' }));
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000');
});
Because req is a readable stream, POST JSON payloads arrive in chunks rather than all at once. Collect chunks inside an async loop (for await (const chunk of req)), enforce a maximum byte limit so huge payloads cannot exhaust RAM, and parse the combined string inside try / catch.
return after res.end(): Continuing into a fallback 404 branch after sending 200 throws ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent.req.url without parsing: Raw req.url includes query parameters (/users?page=2). Parse it with new URL(req.url, 'http://localhost') to separate pathname from searchParams.server.requestTimeout and reject oversized request bodies with HTTP 413.http.createServer() receive two requests for / in a browser?Browsers automatically request /favicon.ico alongside /. Return 204 No Content for /favicon.ico if you want clean request logs.
http directly for HTTPS in production?Use node:https with TLS certificates for direct TLS termination, or run plain node:http behind a reverse proxy like Nginx or Cloudflare.
node:http?Node.js automatically lowercases all incoming header names on req.headers, such as req.headers['authorization'] or req.headers['content-type'].
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
Experiment with the code from this lesson in our interactive playground.