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.
| Concept | What it means | Decision rule |
|---|---|---|
| Request | A method, target, headers, and optional body sent by a client | Treat every part as untrusted input and validate it at the boundary |
| Response | A status, headers, and optional body returned exactly once | Set status and headers before ending or streaming the body |
| Representation | A format such as application/json used to describe a resource | Declare the media type and encode the body to match it |
| Server lifecycle | Start listening, accept requests, stop accepting, and release resources | Separate 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.
- Write the endpoint contract: method, path, successful response, and expected failures.
- Create one request handler that always completes a response.
- Parse the request URL against a trusted base URL and route on pathname, not the raw string.
- Set an explicit status code and Content-Type for every JSON response.
- Return a consistent JSON error when no route matches.
- Start listening only in the entry point and handle shutdown signals deliberately.
Keep the feedback loop short
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.
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.
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);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.
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
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
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