beprodready
Build Your Own HTTP Server

Stage 2: The Router

stage 2 of 4 · ~25 min · runs in your browser

Objective:Build the pattern-matching engine at the heart of every web framework's routing layer — the thing that maps `GET /users/:id` to a handler.

path segment matchingnamed route parameters404 vs 405 distinction (REST semantics)first-match routingO(n) linear scan vs trie routing

Every framework's app.get("/users/:id", handler) is a tiny pattern matcher. Build it.

Implement createRouter() returning:

  • add(method, pattern, handler) — register a route; pattern segments starting with : are parameters (/users/:id/posts/:postId)
  • handle(method, path) — find the matching route and return handler({ params }); return { status: 404 } if no pattern matches, and { status: 405 } if a pattern matches but with the wrong method (real servers distinguish "no such resource" from "wrong verb").

Matching rules:

  • Exact segment count required
  • Literal segments must equal exactly
  • :name segments capture whatever is in that position
  • First registered match wins (order matters)

Why 404 vs 405 matters: If GET /users/1 exists but a client sends DELETE /users/1, a 404 says "this resource doesn't exist" — which is wrong. A 405 says "the resource exists but that verb is not allowed" — which is correct and lets the client know to change their approach.

Why this matters in production

Express's `app.get("/users/:id", handler)`, Fastify's route registration, Next.js's file-based router — they all reduce to a function that matches a method + path against registered patterns and extracts named parameters. Understanding how this works explains why `:id` in Express and `[id]` in Next.js are the same concept with different syntax.

tests (6)

routes a literal path

captures path parameters

unknown path is 404

wrong method is 405 not 404

first registered match wins

root path matches

createRouter.js