# ADR 0006 — Route groups and GET/DELETE query params

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0006-route-groups-query-params.md

---

# ADR 0006 — Route groups and GET/DELETE query params

- **Status:** Accepted
- **Date:** 2026-05-13

## Context

Two gaps surfaced when preparing real projects to run on stitchkit:

1. `buildContext()` ignored `inputSchema` for GET and DELETE — every `list`
   endpoint with filters (`?status=active&limit=20`) was broken.
2. Some projects mount services under a path prefix
   (`/bots/:botId/...`, `/api/{prefix}/...`). stitchkit only built flat paths.

## Decision

**Query params on GET and DELETE.** GET and DELETE parse `inputSchema` from the
URL query. `searchParams.getAll()` supports repeated keys as arrays
(`?tag=a&tag=b`). DELETE sniffs the content-type: a JSON body when
`application/json`, the query otherwise. Coercion is the schema author's
responsibility — query params are always strings; a schema that wants a number
uses `z.coerce.number()`. The framework adds no automatic coercion (rejected:
auto-coerce primitives — magic; a `withQueryCoercion` helper — redundant with
`z.coerce.*`).

**Route groups.** `ServerConfig` accepts `groups: RouteGroup[]` alongside flat
`services`. A `RouteGroup` has a `pathPrefix`, its `services`, and optional
per-group `hooks`. `:param` segments from the prefix (`:botId`) are matched and
placed into the context. (Rejected: a `scopePaths` map keyed by the scope string
— ties URL structure to the scope vocabulary; a mutable `ServiceDef.pathPrefix`
— mutating the service after `implement()` is dirty.) Flat `services` remain
valid, so the simple case stays simple.

## Consequences

- List endpoints work with query filters; arrays are supported.
- Grouping is explicit and decoupled from the scope vocabulary; per-group hooks
  enrich the context (e.g. resolve `:botId`) before the handler runs.
- Coercion stays in the contract — no framework magic to reason around.
