Express.js / Basic: request and response ownership / Lesson 2
Routes, methods and parameter contracts
Match a path and HTTP method precisely while treating route parameters and query values as untrusted inputs.
On this page
Lesson goals
- Separate path parameters, query strings and request bodies.
- Apply strict identifier conversion and Express 5 path syntax deliberately.
A route combines a method and a path contract
app.get registers behavior for a GET route, while app.post registers a different method even when the path text is identical. Route matching uses the path portion of the URL; the query string is not part of the route path pattern. A client requesting /courses/c1?locale=ar still targets a course path with a separate query input. Keep safe read behavior under GET and define writes through appropriate methods with domain checks. Merely changing a method does not implement authorization, CSRF protection or an idempotency policy for repeated requests.
Read parameters from their declared source
The fragment reads id from req.params and locale from req.query, then calls a synthetic domain service. Query parser settings influence the shape of req.query, so a value is not guaranteed to be a scalar string. The explicit locale check rejects arrays or objects instead of passing them deeper into the application. A real implementation should decide whether to ignore unknown query keys or reject them under its public contract. The route remains non-runnable because its app and catalog dependencies are illustrative and no HTTP server is created.
app.get("/courses/:id", async (req,res) => {
const locale = req.query.locale ?? "en";
if (typeof locale !== "string" || !["en","ar"].includes(locale)) {
return res.status(400).json({code:"INVALID_LOCALE"});
}
const card = await catalog.publicCard(req.params.id, locale);
if (!card) return res.status(404).json({code:"COURSE_NOT_FOUND"});
return res.json(card);
});
A missing locale defaults to en; a scalar ar is accepted. An array, object or unsupported string receives the declared 400 result before the lookup.
Use the selected major version’s route grammar
Express 5 changed path string syntax compared with Express 4. Wildcards need names, and optional portions use braces rather than relying on older question-mark patterns. A named wildcard can produce an array of matched path segments, so do not assume every parameter is one scalar string. Review the official migration guide for the exact pattern and the distinction between a wildcard excluding the root and one including it. Avoid copying an old catch-all recipe into a new app without checking representative paths, missing segments and the root path.
Reject partial numeric conversions
parseInt accepts a numeric prefix, which can turn a string such as 12notes into 12 if used without a preceding contract check. The offline parser below accepts only positive decimal identifiers within the safe integer range and rejects leading zeroes deliberately. That is a local application convention, not a universal rule for IDs. Many APIs should preserve opaque textual IDs instead. The key is to state the intended syntax and range before converting, then test invalid strings, zero and oversized values rather than letting JavaScript coercion silently define identity.
function positiveId(text) {
if (typeof text !== "string" || !/^[1-9][0-9]*$/.test(text)) return null;
const value = Number(text);
return Number.isSafeInteger(value) ? value : null;
}
console.assert(positiveId("12") === 12);
console.assert(positiveId("12notes") === null);
console.assert(positiveId("0") === null);
console.assert(positiveId("9007199254740992") === null);
The valid fixture returns 12 and the three invalid fixtures return null. No Express decoding or route matching is simulated.
Order specific routes and define unmatched behavior
Express evaluates matching handlers in registration order, so a broad parameter route can capture a fixed segment intended for a more specific handler. Place /courses/search before /courses/:id when that distinction is part of the contract, or choose a less ambiguous path design. A missing route is normally handled by final ordinary middleware rather than an error handler receiving a thrown exception. Document whether unsupported methods receive a deliberate 405 policy or simply fall through to the application’s unmatched response. Test matching and method behavior separately from domain lookup absence.
Common errors and fixes
search is treated as a course ID
- Why it happens
- A broad parameter route was registered before the fixed search route.
- How to fix it
- Order the specific route first or redesign the ambiguous paths.
- Prevent it next time
- Keep fixtures for reserved fixed segments.
12notes resolves record 12
- Why it happens
- Prefix parsing replaced strict identifier validation.
- How to fix it
- Validate the whole text and range before conversion.
- Prevent it next time
- Test trailing text and unsafe integer boundaries.
A query object reaches a string-only service
- Why it happens
- req.query shape was trusted without validation.
- How to fix it
- Check type and allowed values at the request boundary.
- Prevent it next time
- Test repeated keys and structured query input under the selected parser.
Check your understanding
1. Why is /courses/c1?locale=ar not a separate route pattern containing ?locale?
Show worked solution
The route pattern matches the path, while locale arrives through the query parser and needs a separate input contract. The same path can be called with many query values without registering each URL variant. Validate the resulting value’s type and vocabulary; matching /courses/:id does not certify the query member or make it safe for a downstream service.
2. Should every API reject IDs with leading zeroes as the local parser does?
Show worked solution
No. That is the declared convention of this example. An API with opaque textual identifiers might preserve 0012 as distinct from 12. Choose identity semantics from the domain and source contract, then validate consistently. The lesson is to avoid accidental partial conversion, not to impose one numeric identity design on all applications.
Key takeaways
- A route match and an input validation result establish different facts.
- Express 5 path syntax and parameter shapes deserve explicit migration tests.