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:

  1. Request arrives.
  2. Router finds the first matching route.
  3. Control is handed to the handler.
  4. 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 123

An endpoint is a combination of:

  1. The method (what action you want)
  2. 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:

  1. The HTTP method
  2. 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 /users

This 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 example

8.4.2 Dynamic segments

Routes can include dynamic segments, which act as placeholders.

GET /users/:id

Here, :id matches any single path segment in that position.

This means:

GET /users/1
GET /users/42
GET /users/abc

all 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/123

Even 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/me

If 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/:id

This 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/:id

A request to:

GET /users/123

results 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/123

They usually identify what resource is being acted on.

Query parameters appear after the ?:

/users?page=2&limit=10

They 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.