Phase 2 · Web Servers & HTTP APIsModule 9~54 min read

HTTP Fundamentals & Your First Server

Understand the HTTP request-response contract and build a small JSON web server directly with Node.js core APIs.

What you'll learn

HTTP is a message protocol: a client sends one request and a server returns one response. You will inspect that contract and implement it directly with node:http before a framework hides the mechanics.

By the end of this lesson, you'll be able to:

  • Identify the method, target, headers, optional body, status, and response body in an HTTP exchange
  • Choose appropriate methods, status codes, and media types for simple endpoints
  • Create and start a JSON server with node:http
  • Parse request URLs and close the server cleanly

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
RequestA method, target, headers, and optional body sent by a clientTreat every part as untrusted input and validate it at the boundary
ResponseA status, headers, and optional body returned exactly onceSet status and headers before ending or streaming the body
RepresentationA format such as application/json used to describe a resourceDeclare the media type and encode the body to match it
Server lifecycleStart listening, accept requests, stop accepting, and release resourcesSeparate app creation from listening so startup and tests remain controllable

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Write the endpoint contract: method, path, successful response, and expected failures.
  2. Create one request handler that always completes a response.
  3. Parse the request URL against a trusted base URL and route on pathname, not the raw string.
  4. Set an explicit status code and Content-Type for every JSON response.
  5. Return a consistent JSON error when no route matches.
  6. Start listening only in the entry point and handle shutdown signals deliberately.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Build the smallest useful JSON server

createServer receives an IncomingMessage and a ServerResponse for every request. The response is complete only after res.end is called.

server.js
import { createServer } from "node:http";

const server = createServer((request, response) => {
  const body = JSON.stringify({ message: "Hello from Node.js" });

  response.statusCode = 200;
  response.setHeader("Content-Type", "application/json; charset=utf-8");
  response.end(body);
});

server.listen(3000, "127.0.0.1", () => {
  console.log("API listening at http://127.0.0.1:3000");
});

Inspect method, path, and query input

The URL constructor separates pathname and search parameters. The Host header provides the authority for this local HTTP/1.1 request.

request-info.js
import { createServer } from "node:http";

const server = createServer((request, response) => {
  const url = new URL(request.url, "http://" + request.headers.host);
  const payload = {
    method: request.method,
    pathname: url.pathname,
    search: Object.fromEntries(url.searchParams),
    userAgent: request.headers["user-agent"] ?? "unknown",
  };

  response.writeHead(200, {
    "Content-Type": "application/json; charset=utf-8",
  });
  response.end(JSON.stringify(payload, null, 2));
});

server.listen(3000);
Try http://localhost:3000/search?q=node&page=2 and inspect the JSON response.

Route explicitly and shut down cleanly

The handler completes both known and unknown paths. Signal handlers stop accepting new connections and let the process finish after close completes.

health-server.js
import { createServer } from "node:http";

const server = createServer((request, response) => {
  const url = new URL(request.url, "http://localhost");

  if (request.method === "GET" && url.pathname === "/health") {
    response.writeHead(200, { "Content-Type": "application/json" });
    response.end(JSON.stringify({ status: "ok" }));
    return;
  }

  response.writeHead(404, { "Content-Type": "application/json" });
  response.end(JSON.stringify({ error: "Route not found" }));
});

server.listen(3000);

function shutdown(signal) {
  console.log(signal + " received; closing server");
  server.close((error) => {
    if (error) process.exitCode = 1;
  });
}

process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);

Production practice

Complete once

Every request path must end or stream one response; return after responding so later branches cannot write again.

Declare formats

Set Content-Type to match the body and include a charset for text formats when appropriate.

Control lifecycle

Export server/app creation separately from listening so tests and shutdown code can own the socket.

Common failure mode

Setting a JSON body without the correct Content-Type forces clients to guess. Sending headers after res.end or after body bytes have started causes runtime errors and broken responses.

Independent workshop

Build a native Node.js service with GET /health, GET /time, and GET /greet?name=... endpoints plus a JSON 404 response.

Your finished workshop must include:

  • A written contract for every endpoint
  • Correct status and Content-Type headers
  • Validation for a missing or blank name
  • No response path that can hang or respond twice
  • SIGINT and SIGTERM shutdown handling

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • HTTP is a request-response protocol
  • Methods and paths express intent
  • Statuses describe outcomes
  • Headers describe metadata such as media type
  • node:http exposes the protocol directly
  • A server needs an intentional startup and shutdown lifecycle

Quick check

1. Which response element tells the client the representation format?

2. What does a 404 status communicate?

3. When is a ServerResponse complete?

4. Why separate server creation from listen()?

Next: Requests, Responses & Native Routing