ADR-0013: Centralized API layer & server-state discipline
- Status: Accepted
- Date: 2026-06-05
- Scope: core (any frontend consuming a REST API)
Context
API access decays into chaos through three habits: URLs hardcoded at call sites
(unsearchable, drift-prone), React Query cache keys written as ad-hoc arrays
(invalidation misses them), and auth/error handling re-implemented per request.
Each is invisible at review time and expensive at debug time.
Decision
Three single sources of truth per app:
src/api/endpoints.ts— every endpoint as anENDPOINTSconst
(path-only builders for parameterized routes). Never hardcode a URL at a
call site.src/api/queryKeys.ts— a query-key factory
(queryKeys.<domain>.list(params)/.detail(id)/.all). Never write a
raw key array.src/api/axios.ts— one client instance owning cross-cutting concerns
via interceptors (bearer token injection; 401 → clear session + redirect;
app-specific response handling). Request code never deals with auth.
Server-state conventions (React Query):
- Mutations use
mutateAsyncwhen the caller needs toawaitor catch;
on success, invalidate the relevant query keys (via the factory) so the
cache never serves stale entities. DirectsetQueryDatais the exception for
instant-feedback updates, not the rule. - Shared types mirror backend DTO names exactly (see the backend’s
0011-dto-design-rules) — a DTO name is a cross-repo search key. Top-level
types stay complete; slim embedded types follow<Parent><Child>Response.
Consequences
- “Where is this endpoint used?” is one grep; invalidation can’t miss renamed
keys; auth changes happen in one file. - New developers (and agents) extend tables instead of inventing plumbing.
Related
- 0001-feature-vertical-slices — where the api/ layer sits in the structure
- Backend counterpart: 0011-dto-design-rules
- 0013-sessions-and-refresh-tokens (core backend) — with refresh tokens, the 401
interceptor refreshes and replays the request instead of clearing the session (its §7)