API Route Style

Conventions for HTTP API endpoints (Next.js Route Handlers and equivalents). Handlers are thin: parse, validate, delegate, map to a status code. Business logic lives in services, not in the route.

Next.js Route Handlers live in app/.../route.ts and export functions named for HTTP verbs (GET, POST, ...) that return a Response. Source: Next.js, https://nextjs.org/docs/app/building-your-application/routing/route-handlers, 2026-05-31

Thin handler, four steps

  1. Parse the request (params, query, body).
  2. Validate every input with a Zod schema; on failure return 400 with a structured error (see Zod Validation Style).
  3. Delegate to a service or domain function that returns a Result (see Error-Handling Style).
  4. Map the result to an HTTP status and a typed response body.

No database queries, no business rules, no branching domain logic inside the handler itself. The route is an adapter between HTTP and your service layer. Source: compiled from TypeScript Style, 2026-05-31

Status codes mean what they say

Use the right code, not 200 with an { ok: false } body: 201 for created, 204 for no content, 400 malformed, 401 unauthenticated, 403 unauthorized, 404 missing, 409 conflict, 422 semantically invalid, 429 rate-limited, 500 for unexpected server faults. Return a consistent error envelope, for example { error: { code, message } }, across every endpoint. Source: compiled from wiki, 2026-05-31

Security and trust

  • Authenticate and authorize first, before parsing anything expensive. Never trust the client.
  • Treat the body as hostile until Zod has parsed it.
  • Do not leak internals: map internal errors to safe external messages at the edge (see Dual-Audience Errors). The rich internal detail belongs in logs, the context channel of Depth-of-Context Interfaces, not the response.

Conventions

  • Validate and read params/searchParams explicitly; coerce with Zod, do not trust string types.
  • Make mutating endpoints idempotent where the protocol allows (accept an idempotency key on POST for payments and similar).
  • Paginate list endpoints with stable cursor or limit/offset conventions; document the shape.
  • Keep response bodies typed end-to-end (infer the type from the Zod output schema).

Route Contract

Step Default Banned pattern Proof
Auth Authenticate and authorize before expensive work Parse/process untrusted body before auth on sensitive routes Unit/integration test for unauthenticated and unauthorized paths
Parse Zod or typed parser at the boundary Trusting string params or JSON shape Invalid body/query test
Delegate Call domain/service function Business logic inside route handler Thin handler review
Map Typed result to HTTP status and envelope 200 with { ok: false } Status-code tests for success and failure
Observe Log internal context safely Leak stack/provider internals to client Error-shape snapshot or assertion

Exceptions

A tiny read-only route may inline simple delegation when it has no domain branch, no auth nuance, and no reusable service boundary. The moment the handler has multiple failure modes, extract the service or parser. Server Functions follow the same trust model; see React Server Components Style.

Validation

Every new mutating route needs tests or request-level proof for valid input, invalid input, unauthenticated access, unauthorized access when distinct, and the main domain failure. The route is not done until status codes prove the contract, not merely the happy path.

Proof

API route proof is the public boundary responding with the expected status and body for success, bad input, auth failure, and domain failure. A service-layer unit test cannot prove the route contract by itself.

Style Rule Contract

This page follows Style Rule Contract: name the default pattern, banned pattern, reason, exception, proof, and codification path clearly enough that the next agent can apply the rule without asking Kevin again. If a local repo has a stricter rule, follow the local rule and update the wiki when the decision becomes durable.


Timeline