The Server Calls You
A Node.js web server is built from handlers: functions with the shape (req: IncomingMessage, res: ServerResponse) => void, from the built-in node:http module. The server accepts connections and calls your handler once per request — req holds everything about the request, and whatever you write into res becomes the response.
A real server starts with server.listen(8080) and runs forever. The examples here call server.listen(0) instead: port 0 means "any free port", so nothing can collide. Each example then reads the real port, makes its own request with fetch, and calls server.close() — so every program runs and finishes by itself.
A First Handler
The smallest possible handler writes a line of text. Everything an API does — JSON, status codes, routing — happens inside this same function shape.
Hello from a handler
The handler writes into res; the client reads exactly that as the response body, with status 200 OK filled in automatically.
Output: TypeScript file and captured output
import { createServer, type IncomingMessage, type ServerResponse } from "node:http"
// The smallest handler: one request in, one line of text out.
function hello(req: IncomingMessage, res: ServerResponse): void {
res.end("hello from the server\n")
}
async function main(): Promise<void> {
const server = createServer(hello)
await new Promise<void>(resolve => server.listen(0, () => resolve()))
const port = (server.address() as { port: number }).port
const res = await fetch(`http://localhost:${port}/`)
const body = await res.text()
console.log("status:", res.status, res.statusText)
process.stdout.write("body: " + body)
server.close()
}
main()
Answering with JSON
An API handler does three things: set the Content-Type header, choose a status code, and write the data. res.writeHead(200, { "Content-Type": "application/json" }) does the first two, and res.end(JSON.stringify(value)) writes the body.
Order matters: the status and the headers must be set before the body is written.
A JSON endpoint
The handler turns a UserRecord into JSON text and sends it. The client receives valid JSON and the right content type — this is the skeleton of every endpoint in the rest of this group.
Output: TypeScript file and captured output
import { createServer, type IncomingMessage, type ServerResponse } from "node:http"
interface UserRecord {
id: number
name: string
}
function userHandler(req: IncomingMessage, res: ServerResponse): void {
const user: UserRecord = { id: 1, name: "Ana" }
// Headers and status first, then the body.
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify(user))
}
async function main(): Promise<void> {
const server = createServer(userHandler)
await new Promise<void>(resolve => server.listen(0, () => resolve()))
const port = (server.address() as { port: number }).port
const res = await fetch(`http://localhost:${port}/user`)
const body = await res.text()
console.log("status:", res.status, res.statusText)
console.log("type: ", res.headers.get("content-type"))
console.log("body: ", body)
server.close()
}
main()