2.4 · Creating a native HTTP server

Level 2 · Node.js Core

2.4Creating a native HTTP server

Goal: Create an HTTP server with the native module (req/res, manual routing, HTTP codes and headers) and understand the foundation Express is built on.
Estimated time: 11 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

The essentials in 30 seconds

  • A server in a few lines: createServer((req, res) => ...) then .listen(port) — each request triggers the function.
  • req reads, res answers: method, url and headers on one side; status, headers and body on the other — the HTTP vocabulary of APIs.
  • By hand, then Express: routing with ifs and parsing the body shows what Express simplifies — never a black box.

Ongoing mini-case — Salim serves the inventory in the browser

Final act: Salim wants the team to browse the inventory (2.1) from the browser, with nothing installed. He creates a server with the http module: one route listing reports as JSON, one showing the home page as HTML, a 404 for the rest — all routed by hand on req.method and req.url. Struggling with body parsing and the if cascade, he understands exactly what Express (level 3) will simplify for him. The level 2 project is delivered: files, events, streams, server.

Epilogue to the 2.1–2.3 thread: Salim's tooling becomes a server the team can browse.

1. A web server with no dependency

Node creates web servers and APIs with its native http module, with zero dependencies — and doing it “by hand” BEFORE Express is very instructive. You create a server with http.createServer(), passing a function called on EVERY request: it receives req (request: GET/POST method, URL, headers, body) and res (response: what you build and return). Then .listen(port) listens for connections (e.g. 3000). A few lines for a working server: Salim shows the inventory in the team's browser in about ten lines.

2. req, res and the HTTP vocabulary

On the request side, req provides req.method (GET, POST, PUT, DELETE…), req.url (the path, e.g. /rapports), req.headers — and for a POST, the body reads like a stream (section 2-3), chunk by chunk. On the response side, res builds: res.statusCode = 200, res.setHeader('Content-Type', 'application/json'), res.write(...) then res.end(...). You must know HTTP: methods (GET read, POST create, PUT/PATCH update, DELETE delete — the REST API vocabulary, section 4-1), codes (200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error…), headers (content type, authentication, cache…).

3. All by hand — then Express

Natively, you handle EVERYTHING by hand: routing on URL and method (if cascade), parsing the body, setting headers, handling errors — quickly tedious for a real application. That's where Express (section 3-1) steps in: elegant routing, body parsing, middlewares — but everything rests ON the http module. Creating a native server is therefore a founding exercise: it reveals the foundation (req/res, HTTP, ports) and shows what a framework brings. You won't write native servers in production (you'll use Express), but this understanding is the difference between using Express knowingly and suffering it as a black box — including for debugging when the “magic” fails.

Common misinterpretation: “Since I'll use Express, I can skip the native server”. No. Without the native server, Express is magic: app.get() “works” without you knowing that a request arrives, that you must route on method and URL, set status and headers, send a body. Having routed by hand and parsed a streamed body, you MEASURE what Express simplifies — and when a header is missing or a body isn't parsed, you can diagnose instead of being helpless. A few lines of native save months of black box: Salim routed three paths by hand before touching Express.
A request's lifecycle From client to response Client Browser: GET/rapportsServer createServer: functioncalled on each requestRouting if on req.method+ req.urlResponse res: status,headers, body, end()
From client to response
Text version of the diagram (SEO / screen readers / translation): 1) The CLIENT sends a request: method + path + headers (e.g. GET /rapports). 2) Salim's SERVER receives it: http calls his function with (req, res) — the server is an EventEmitter that emits on each request. 3) He ROUTES by hand: ifs on req.method and req.url pick the response (JSON list, HTML home, 404). 4) He ANSWERS with res: statusCode, Content-Type, body via end(). Lesson: this manual cycle is the foundation — Express automates it without replacing it.

Did you know?

Salim showed his “ten-line” server to the team — and his lead thought he had installed a framework.
True: the native http module is enough to serve a page and an API, with nothing installed.

Ten lines of native beat a framework you don't understand.

🔎 Concrete example — 🔎 Concrete example — Salim's inventory server

Three hand-routed paths, as at Salim's:

GET /          → 200, home page HTML
GET /rapports  → 200, inventory as JSON (Content-Type: application/json)
Other path     → 404, error message
POST /rapports → 201 after reading the body as a stream

Reading: each method + URL combination has its response and status.

Lesson: you measure what Express will simplify (routing, parsing).

Section vocabulary

http (native module)
Module that creates a web server with no dependency: http.createServer() with a function called on each request, then .listen(port).

Simple example: Salim serves the inventory on port 3000 in ten lines.

What it is not: a framework like Express (which rests on it).

Reflex: understand the foundation before using the framework.

req (request)
The incoming request object: method (req.method), path (req.url), headers (req.headers), body read as a stream for POST.

Simple example: Salim routes on req.method and req.url.

What it is not: the object you build the response with (that's res).

Reflex: reading the request = req.

res (response)
The response-building object: status (res.statusCode), headers (res.setHeader), body (res.write/res.end).

Simple example: Salim sends res.statusCode = 200 then res.end(JSON.stringify(...)).

What it is not: the request sent by the client (that's req).

Reflex: building the response = res, and always finish with end().

HTTP methods & codes
Methods: GET (read), POST (create), PUT/PATCH (update), DELETE (delete). Codes: 200 OK, 201 Created, 400/401/404 (client), 500 (server).

Simple example: GET /rapports → 200, POST /rapports → 201, unknown path → 404.

What it is not: a 200 returned for everything, successes and errors.

Reflex: each response = the right verb + the right code.

What Express simplifies
Native handles everything by hand (if routing, body parsing, headers); Express provides routing, parsing, middlewares — on top of the http module.

Simple example: Salim replaces his if cascade with app.get('/rapports', ...) in level 3.

What it is not: a competing server that replaces http.

Reflex: tedious by hand = need covered by Express.

In practice — In practice — Creating a native HTTP server

  1. Create a server with http.createServer((req, res) => {...}) and start it with .listen(3000); test in the browser (localhost:3000).
  2. Return a response: set res.statusCode, res.setHeader('Content-Type', ...) and finish with res.end('Hello').
  3. Route “by hand” on req.method and req.url (an if cascade): return different responses per path.
  4. Return JSON (Content-Type application/json + res.end(JSON.stringify(data))) and notice how handling everything by hand quickly gets tedious.
You create an HTTP server with the native module (req/res, manual routing, HTTP codes and headers) and understand the foundation Express is built on — the “under the hood”.

Ready-to-say script — defending the native exercise before Express (30 s)

“Before Express, I routed three paths by hand: I know what a request is, how you pick the response from method and URL, and why each status matters. Now, when I write app.get, I'll know what Express does for me — and when it breaks, I'll know where to look.”

Say it while showing the native server running on localhost:3000; the in-browser demo convinces.

Copy-paste template — native server with manual routing

const http = require('node:http');

const rapports = [
  { nom: 'rapport-01.txt', taille: 1240 },
  { nom: 'rapport-02.txt', taille: 986 }
];

const serveur = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/') {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.end('

Report inventory

'); } else if (req.method === 'GET' && req.url === '/rapports') { res.statusCode = 200; res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.end(JSON.stringify(rapports)); } else { res.statusCode = 404; res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.end('Not found'); } }); serveur.listen(3000, () => console.log('Server at http://localhost:3000'));

Local demo

localhost:3000 in front of the team: Salim's inventory shows in the browser, zero deployment.

Team JSON API

GET /rapports returns the inventory as JSON: colleagues consume it from their own scripts.

Big file to serve

Sending the 2 GB export: pipe a createReadStream into res (section 2.3) instead of loading everything.

Towards Express

As soon as the ifs pile up: app.get('/rapports', ...) replaces the cascade — same http foundation underneath.

✓ Best practices

  • Test each route in the browser or with curl (method + URL + status).
  • Always return the right status (201 after creation, 404 if unknown, 500 on error).
  • Finish every response with res.end(), otherwise the client waits forever.

✗ Common pitfalls

  • Skipping the native server and suffering Express as a black box.
  • Returning 200 for everything, successes and errors.
  • Forgetting res.end() (request hanging forever).

Key takeaways

  • Node creates web servers with the native http module (no dependency): http.createServer((req, res) => ...) then .listen(port). Instructive BEFORE Express.
  • req = the request (method, url, headers, streamed body); res = the response (statusCode, setHeader, write/end).
  • Know HTTP: METHODS (GET/POST/PUT/DELETE) and STATUS CODES (200, 201, 400, 401, 404, 500) — the REST API vocabulary.
  • The native server handles EVERYTHING by hand (if routing, body parsing, headers) → quickly tedious; Express simplifies it but RESTS on http.

48h challenge — apply it tomorrow

Put the template server online with your real data (your 2.1 inventory): HTML home, JSON route, neat 404. Show it to a colleague in their browser, then note in one sentence what Express will simplify for you.

Section exit checklist
  • I can explain the request → routing → response cycle.
  • I can serve HTML and JSON with the right statuses.
  • A colleague browsed my native server in their browser.
Self-rating 1-5: do I master req/res and statuses?do I understand what Express does for me?

Frequently asked questions

Why learn the native HTTP server if I'll use Express anyway?

To use Express with understanding instead of suffering it. Having routed by hand (if cascade), parsed a streamed body and set headers, you KNOW what really happens when you write app.get() — and you MEASURE what Express simplifies (elegant routing, express.json(), middlewares). Above all, you debug: missing header, wrong status, unparsed body — you know where to look instead of being helpless when the “magic” fails. Like Salim: three paths by hand before the first app.get.

What HTTP basics must I absolutely know?

The request → response cycle, the METHODS (GET read, POST create, PUT replace, PATCH partially update, DELETE delete — one URL does different things per method), the STATUS CODES (2xx success: 200 OK, 201 Created; 4xx client error: 400, 401, 403, 404; 5xx server error: 500 — a good API returns the right code, not 200 for everything), and the HEADERS (Content-Type, authentication, cache). This vocabulary structures REST APIs (section 4-1) and is practised from the native server: each of Salim's routes returns the right method, status and Content-Type.

Check your understanding

An unknown path hits Salim's native server. What should it return?

Additional resources (optional, to go further)

Level 2 examFresh, harder questions to test what you've learned