8 Routing
Routing is a key component of ambiorix, having a good understanding it of it is crucial, thankfully it is not complicated.
8.1 What?
Routing isn’t something that is specific to Ambiorix, Express JS, Django, or any other web framework you know of. Every single backend system that has ever been built has routing.
The entire purpose of routing is to answer one question:
Given an incoming request, which piece of code should handle that request?
The router’s only job is pattern matching and dispatch. It doesn’t care what happens afterwards - that’s the handler’s problem.
In essence, routing doesn’t do the work. It just decides where the work should happen. As such, the narrative is:
- Request arrives.
- Router finds the first matching route.
- Control is handed to the handler.
- Router is done.
8.2 Analogy
All analogies fail in some way in their comparisons because they are not the real thing, but allow me to lean on them here.
Routing is like a mail sorting facility.
Letters come in with addresses on them. The facility doesn’t read the contents - it just looks at the address, and forwards the letter to the correct department for processing.
/users/123 → controllers/users.R → get_user(123)
/orders → controllers/orders.R → list_orders()You can think of it as a switch() statement on steroids, where cases are URL patterns instead of literal values.
8.3 Endpoints
An endpoint is a specific address where a service accepts requests and does something.
Extending the mail analogy: If the router is the sorting facility, then an endpoint is a desk within a department. Each desk handles one type of request.
POST /users → desk that creates new users
GET /users/123 → desk that fetches user 123
DELETE /users/123 → desk that deletes user 123An endpoint is a combination of:
- The method (what action you want)
- The path (where to direct it)
It’s more of a contract between the client and the server:
Send this shape of request to this address, get this shape of response back.
8.4 Matching Rules
When a request reaches the server, the router evaluates it against a list of known routes and tries to find a match.
A match is determined using two pieces of information:
- The HTTP method
- The request path
Both must match for a route to be selected.
8.4.1 Static paths
The simplest form of matching is a literal comparison.
GET /usersThis route only matches requests made with the GET method to the exact /users path. No more, no less.
A request to /users/ or /users/123 is not the same thing, and will not match unless explicitly defined.
# provide example8.4.2 Dynamic segments
Routes can include dynamic segments, which act as placeholders.
GET /users/:idHere, :id matches any single path segment in that position.
This means:
GET /users/1
GET /users/42
GET /users/abcall match the same route pattern.
Dynamic segments match structure, not meaning. The router does not care whether id is numeric, a UUID, or complete nonsense. That responsibility belongs elsewhere.
8.4.3 Method-specific Matching
The HTTP method is always part of the match.
These are three completely different routes:
GET /users/123
PUT /users/123
DELETE /users/123Even though the path is identical, the intent is different, and so the handler is different.
This is why routing is about intent, not just URLs.
8.4.4 Order matters
Routers evaluate routes in the order they are defined.
The first route that matches wins.
If a general route is defined before a more specific one, it may capture requests you did not intend.
Conceptually:
/users/:id
/users/meIf evaluated top-to-bottom, /users/me would match the first route, not the second.
Internally in Ambiorix, we always re-order the routes so that more specific and shorter routes come first, then general and longer routes come last.
This means Ambiorix would reorder the above to:
/users/me
/users/:idThis makes it easier for you when building an application since you don’t have to worry about the order anymore.
8.5 Parameter extraction
When a route with dynamic segments matches, the route extracts values from the path and makes them available to the handler.
Using this route:
GET /users/:idA request to:
GET /users/123results in the value:
id = "123"The router’s job ends at extraction.
It does not:
- Validate the value
- Convert it to a number
- Check whether it exists in a database
It simply captures the string and passes it along.
8.5.1 Path parameters vs query parameters
There are two common ways data is attached to a request URL.
Path parameters are part of the route structure:
/users/123They usually identify what resource is being acted on.
Query parameters appear after the ?:
/users?page=2&limit=10They usually modify how the request should behave.
Both are just strings from the router’s perspective.
8.6 Conclusion
Routing stops at structure.
A useful mental model is this:
- Routing answers “which handler?”
- Parameters answer “with what raw inputs?”
Anything beyong that (validation, defaults, coercion, authorization, etc.) happens after routing has already finished its job.
This separation keeps routing simple, predictable, and easy to reason about.