Routes & the HTTP surface

The domain layer models the business and the application layer acts on it — but nothing is reachable from outside until you expose it. The presentation layer is where operations become an API. A routes block maps commands, queries, and use cases onto HTTP verbs and paths — the one place the outside world touches the system.

Chowk's ordering surface:

sub-domains/ordering/routes/order.vishwakarma
routes OrderingRoutes {
  auth protected
  doc  "Chowk's public ordering surface: place an order, add lines, pay, and read orders."

  group "/v1" {
    post "/orders"          : PlaceOrder            systems [OrderingSystem] implements "Order lifecycle" for customer maturity "beta" implements OrderingApi
    post "/order-lines"     : AddOrderLine          systems [OrderingSystem] implements "Order lifecycle" for customer maturity "beta" implements OrderingApi
    post "/orders/{id}/pay" : MarkOrderPaid         systems [OrderingSystem] implements "Order lifecycle" for customer maturity "beta" implements OrderingApi
    get  "/orders/{id}"     : GetOrder   errors[404] systems [OrderingSystem] implements "Order lifecycle" for customer maturity "beta" implements OrderingApi
    get  "/orders"          : ListOrdersByCustomer  systems [OrderingSystem] implements "Order lifecycle" for customer maturity "beta" implements OrderingApi
  }
}

A route binds a verb + path to an operation

Reading one line — post "/orders" : PlaceOrder — left to right:

  • post — the HTTP method (get, post, put, patch, delete).
  • "/orders" — the path, relative to the enclosing group. A {id} segment is a path parameter, bound by name to the operation's input (/orders/{id}/pay supplies id to MarkOrderPaid).
  • PlaceOrder — the operation the route dispatches. It is referenced by name (pulled in with use ordering.PlaceOrder at the top of the file); a route never contains logic, it only routes to an operation authored in the application layer.

The convention that reads well: get for queries, post for creates and state transitions, the resource in the path, and the operation on the right.

group — a shared path prefix

A group "/v1" { … } prefixes every route inside it, so the paths above are really /v1/orders, /v1/orders/{id}/pay, and so on. Groups keep a versioned or resource-scoped prefix in one place instead of repeated on every line.

auth — the authentication posture

auth protected declares that every route in the block requires an authenticated caller — the CallerMiddleware resolves the caller's identity (and RLS scope) before the operation runs, and an unauthenticated request is rejected at the edge. This is the surface-wide default; it's why a command's guard and a query's where can rely on caller.* being populated. A public, unauthenticated surface would declare a different posture — but a marketplace's order operations are protected.

errors — the typed failures a route can return

get "/orders/{id}" : GetOrder errors[404] declares that this route can return a 404. Declaring the error codes a route may produce is what lets the generated client model them as typed outcomes rather than opaque failures — the SDK knows a GetOrder can 404, and a caller can handle it. The codes tie back to the domain's declared errors and the operation's guards.

The overlay hooks: systems and implements

Two trailing clauses on each route are its architecture-overlay attribution — the bottom-up link from a built endpoint back to the plan:

  • systems [OrderingSystem] — the capability system this route belongs to. It's how the route rolls up under the right node in the architecture map (see System & Capability Modeling).
  • implements "Order lifecycle" for customer maturity "beta" — the feature this route realizes, for which audience, at what maturity. This is what reconciles the built reality against the planned feature (see Features).
  • implements OrderingApi — the UI surface (a kind endpoint surface) this route realizes, closing the capability ◀exposes◀ ui ◀implements◀ route access chain (see UI surfaces).

These are optional in the sense that a route works without them — but authoring them is what keeps the architecture map honest: every endpoint declares which capability system it serves, which feature it ships, and which surface it realizes. A route that implements a feature no system planned surfaces as capability drift.

What the presentation layer does not do

A route contains no business logic, no validation, no authorization decision of its own — all of that lives in the operation it dispatches (the command's guard/policy, the query's where, the input's specs). The presentation layer's whole job is the mapping: verb + path + typed errors + overlay attribution → an operation. Keeping it that thin is what lets the same application-layer operation be exposed on more than one surface, or re-pathed, without touching the logic underneath.