Authoring Routes
A route in Slingshot is the unit of HTTP. Whether it’s auto-generated from an entity or
declared in a domain(...), it goes through the same set of concerns: input validation,
authorization, response shaping, error handling, observability. This section is the
canonical home for each one.
What lives on a route
Section titled “What lives on a route”| Concern | Where it’s set | Page |
|---|---|---|
| Path, method, handler | route.get/post/...({ path, handler }) | Routes and Handlers |
| Request validation | request: { body, params, query } (Zod) | Validation |
| Response shape | responses[status]: { schema, transform, dto } + entity dto | DTO Mapping |
| Auth, permission, rate limit, idempotency, events | routes.<op> for entities, route.* for domain routes | Route Policy |
| Per-route logic before/after the handler | middleware: [...] | Middleware |
| Error responses | thrown HttpError or respond.notFound() etc. | Exception Handling |
| Authenticated identity | actor on the handler context | Context and Actors |
| Helper utilities for context | getActor, getRequestTenantId, etc. | Context and Actor Helpers |
| Cursor-based listing | pagination config + respond.json({ items, cursor }) | Pagination |
| Retry-safe writes | idempotency: true on the route | Idempotent Requests |
| Server-generated request ID | automatic — surfaced via c.get('requestId') | Request IDs |
| Versioned API surface | versioning: { ... } in app config | API Versioning |
| Generated docs | automatic from request + responses schemas | OpenAPI |
The two route flavors
Section titled “The two route flavors”Both flavors share every concern listed above. The difference is what generates them:
Entity routes — auto-generated from defineEntity({ ..., routes: { ... } }). CRUD is
free (POST /users, GET /users/:id, etc.). Named operations come from
defineOperations({ publishPost: op.transition(...) }) and become additional routes
under the entity’s path. Concerns are declared per-op:
defineEntity('Post', { fields: { /* ... */ }, routes: { list: { auth: 'none' }, // public list create: { permission: { requires: 'post:write' }, dto: 'default' }, operations: { publish: { permission: { requires: 'post:moderate' } }, }, },});Domain routes — declared explicitly inside definePackage({ domains: [...] }) for
endpoints that don’t map to entity CRUD. Concerns are declared per-route:
route.post({ path: '/preview', auth: 'userAuth', rateLimit: { windowMs: 60_000, max: 30 }, request: { body: PreviewBody }, responses: { 200: { description: 'Rendered', schema: PreviewResult } }, handler: async ({ body, respond }) => respond.json(await render(body.markdown)),});For most concerns the surface is identical — auth, permission, rateLimit,
idempotency, event, middleware work the same way on either side. Validation is
declared on the route in the domain case and auto-derived from defineEntity fields on
the entity case (with inputVariants to gate per-route).
Reading order
Section titled “Reading order”The pages in this section are independent — read whichever ones cover the concern you’re working with. New to Slingshot? Read in this order:
- Routes and Handlers — the basic shape of a route and what the handler receives.
- Validation — Zod schemas at the request boundary.
- DTO Mapping — controlling response shape and gating input fields per variant.
- Route Policy — auth, permissions, rate limits, idempotency, events on entity routes.
- Middleware — per-route or per-package middleware chains.
- Exception Handling — typed errors and the framework’s error envelope.
The remaining pages (Pagination, Idempotency, Request IDs, API Versioning, OpenAPI) are focused topics — open them when you need them.
Next in your journey
Section titled “Next in your journey”Your routes are tight. The journey continues into the data and security layers:
- Working with Data — the entity system in depth, when the route should be auto-generated from a data model.
- Security — auth and permissions on top of those routes.
Adjacent
Section titled “Adjacent”- Composing an App — return here to revisit how packages, entities, and events fit together at the app level.