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:
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 enclosinggroup. A{id}segment is a path parameter, bound by name to the operation's input (/orders/{id}/paysuppliesidtoMarkOrderPaid).PlaceOrder— the operation the route dispatches. It is referenced by name (pulled in withuse ordering.PlaceOrderat 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 (akind endpointsurface) this route realizes, closing thecapability ◀exposes◀ ui ◀implements◀ routeaccess 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.