Node.js HTTP Server

A Node.js HTTP server can be created with just the built-in http module, no frameworks required. Understanding it helps you see what frameworks like Express do for you behind the scenes.

Creating a Basic Server

createServer takes a function that runs for every request. req describes the incoming request (method, URL, headers) and res is how you send a response. res.end finishes the response. Stop the server with Ctrl+C.

import http from 'node:http';

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello from Node.js!');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});
Server running at http://localhost:3000

(browser at http://localhost:3000)
Hello from Node.js!

Routing by URL and Method

const server = http.createServer((req, res) => {
  const { pathname } = new URL(req.url, 'http://localhost');

  if (req.method === 'GET' && pathname === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    return res.end('<h1>Home</h1>');
  }
  if (req.method === 'GET' && pathname === '/api/time') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({ now: new Date().toISOString() }));
  }
  res.writeHead(404, { 'Content-Type': 'text/plain' });
  res.end('Not found');
});
GET /api/time
{"now":"2026-10-31T09:15:02.118Z"}

GET /nope
Not found

Reading the Request Body

Mark the handler async to use await. In production you must also catch invalid JSON and limit body size.

async function readJson(req) {
  const chunks = [];
  for await (const chunk of req) chunks.push(chunk);
  return JSON.parse(Buffer.concat(chunks).toString() || '{}');
}

// inside the request handler
if (req.method === 'POST' && pathname === '/api/echo') {
  const body = await readJson(req);
  res.writeHead(201, { 'Content-Type': 'application/json' });
  return res.end(JSON.stringify({ received: body }));
}
POST /api/echo  {"name":"Hina"}
{"received":{"name":"Hina"}}

Making HTTP Requests with fetch

Node 18+ includes the same fetch function as browsers, so calling other APIs is easy.

const res = await fetch('https://api.github.com/users/octocat');
const user = await res.json();
console.log(user.name, '-', user.public_repos, 'repos');
The Octocat - 8 repos

Common HTTP Status Codes

  • 200 OK, 201 Created, 204 No Content
  • 301/302 Redirect, 304 Not Modified
  • 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
  • 500 Internal Server Error

Why Use a Framework?

The raw http module works, but routing, body parsing, cookies and error handling quickly become repetitive. Express adds these features with a tiny, readable API, which is why the next chapters switch to it.

Frequently asked questions

How do I create a web server in Node.js without Express?

Import node:http, call http.createServer with a (req, res) handler, and call server.listen(port). Use res.writeHead and res.end to send responses.

How do I return JSON from a Node.js server?

Set the Content-Type header to application/json and send JSON.stringify(data) with res.end.

Does Node.js have fetch built in?

Yes. Since Node 18, fetch is available globally without installing node-fetch or axios.