Reading — step 1 of 5
Read
Path Parameters & Query Strings
/users/42/posts/7 — no server registers that path literally; there are a billion possible IDs. Real routing matches patterns (/users/:id/posts/:post_id) and extracts the variables. Add the query string (?page=2&sort=desc) and you have the two channels through which every web API receives its inputs. This lesson builds both extractors.
Pattern routes: match by segments
The registered pattern and incoming path both split on / into segments; matching walks them in lockstep:
pattern: /users/:id/posts/:post_id → [users, :id, posts, :post_id]
request: /users/42/posts/7 → [users, 42, posts, 7 ]
Rules per position: a literal segment must match exactly; a :param segment matches any single segment and captures it. Segment counts must be equal — /users/42 (2 segments) cannot match the 4-segment pattern; no partial credit. Result of a match: the handler plus a params map, {id: "42", post_id: "7"} — and note the values are strings. The router doesn't know :id is numeric; handlers validate and convert (/users/abc routes fine; the handler's "abc isn't an ID" → 404 or 400 is an application decision, cleanly outside the router).
Precedence: the ordering question every router answers
/users/new — literal — and /users/:id — pattern — can both match /users/new. Which wins? The rule almost every framework converged on: static beats dynamic; the most specific match wins, regardless of registration order. Otherwise adding a pattern route can silently steal an existing literal route — the kind of spooky breakage frameworks exist to prevent. Your implementation: check literal-segment matches before param captures at each position (or two-pass: exact routes first, then patterns). Your spec fixes the rule; the concept — specificity, not registration order — is the transferable part.
Query strings: the other input channel
Everything after ? is the query string, and it never participates in routing — strip it before path matching (/search?q=x routes as /search), parse it separately:
?q=redis&page=2&sort= → {q: "redis", page: "2", sort: ""}
Grammar: &-separated pairs, each key=value. The corners that distinguish a parser from a split("&"):
- Percent-decoding:
q=hello%20world→hello world;+also means space in query strings (form-encoding legacy — decode both). - Empty values are present:
sort=is the keysortwith value""— distinct from absent. Some grammars also allow bare keys (?debug) — spec-dependent. - Duplicate keys (
?tag=a&tag=b): legal, meaningful (multi-select forms), policy required — last-wins or list, per your spec. (The same "have a policy" lesson as duplicate headers, and for the same reason.)
Path params vs query params, as design culture: the path identifies which resource (/users/42), the query modifies how you want it (?fields=name&format=json). Nothing enforces it; every well-designed API follows it; now your router makes the distinction natural.
Your exercise: Parameterized Routing
Patterns registered, requests in; handler + extracted params (path and query) out. The ladder: single :param → multiple → literal-vs-pattern precedence → segment-count mismatches → query parsing with decoding, empties, and duplicates per spec. When it passes, you've built the front door of Express/Flask/Fiber — the part of every framework that turns a URL into a function call with arguments, which is to say: the part that makes the web programmable.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…