How to: add a REST endpoint¶
Endpoints are driving adapters over application use cases — they must not contain business logic.
Steps¶
- Start from a use case — define (or reuse) an application command/query in
application/.../commandorapplication/.../query. It depends only on domain ports. - Write the contract test first (Principle II) — add it under
adapters/rest-api/src/test/...againstcontracts/openapi.yaml; make it fail. - Update the contract — add the path/schema to
contracts/lib/routes.tsp(andcontracts/lib/models.tspfor new types), then regenerate:Never hand-editmise run contracts:build # .tsp → openapi.yaml mise run ui:generate-types # openapi.yaml → ui/src/api/types.tsopenapi.yaml— it is generated output. - Add the route in
adapters/rest-api/.../*Routes.kt: parse the request DTO, read theActorContext, call the use case, map the result/errors to HTTP, return the response DTO. - Wire it in the api-service composition root (Koin module or manual wiring).
- Map errors — domain refusals → appropriate HTTP (403 unauthorized, 409 conflict/illegal state, 404 not found, 422 invalid input). Never swallow errors silently.
At any point, verify the build stays green:
mise run build # compile + test + boundary check
mise run lint # ktlint + detekt
Keep the framework out of the core
Ktor types live only in rest-api/apps. The use case knows nothing about HTTP. The boundary
test enforces this.