ADR-0004 API layer
Company standard HTTP API layer — Hono, mounted at /api inside the TanStack Start app.
- Decision
- Hono at /api/*Build the app
- Status
- SupersededSuperseded by ADR-0036
- Date
- Scope
- Company-wide default for all new apps
Connections
Context
Section titled “Context”TanStack Start (ADR-0002) ships three server primitives:
- Server functions — typed RPC calls from our UI to the server. Excellent for UI-internal data flow, but not a public HTTP API: no REST contract, no story for non-React clients (mobile apps, partner integrations, webhooks, scripts).
- Server routes — raw
Request→Responsehandlers on file routes (server.handlers). Real HTTP endpoints, but no schema-validated route definitions and no OpenAPI. - Middleware —
createMiddleware, plus global request middleware insrc/start.tsthat runs for SSR, server routes, and server functions alike.
What Start lacks is an API-first toolkit. We need an API layer that:
- exposes a standard, versionable HTTP API under
/api/* - has a mature HTTP middleware ecosystem (CORS, auth, secure headers, logging) for the API surface — auth (ADR-0009) plugs in here; rate limiting runs one layer earlier, in Start’s global request middleware (ADR-0010)
- runs inside the same Docker container as the app (ADR-0003) — no separate service to operate
- integrates with our validation library, Zod (ADR-0005)
Options considered
Section titled “Options considered”1. TanStack Start server functions only
Section titled “1. TanStack Start server functions only”Use built-in server functions for everything, no separate API framework.
Pros
- Zero new dependencies; fully typed end to end
- One mental model for server code
Cons
- RPC-shaped, not REST — no standard HTTP contract for external clients
- No OpenAPI generation; no auto-generated clients for non-TS consumers
- Webhooks and third-party integrations become awkward
2. TanStack Start server routes
Section titled “2. TanStack Start server routes”Plain HTTP handlers on Start’s file routes, sharing Start’s middleware.
Pros
- Zero new dependencies; endpoints live next to the routes they serve
- Same middleware chain as SSR and server functions
Cons
- No schema-validated route definitions and no OpenAPI generation — contracts for external clients are hand-maintained
- Small HTTP middleware ecosystem compared to Hono
- Coupled to Start — no path to extract the API into a standalone service
3. Hono
Section titled “3. Hono”Lightweight web-standard framework (Request/Response APIs), tiny, runs on any runtime — Node, Bun, edge. Mounts directly inside TanStack Start’s server handler.
Pros
- Web-standard fetch API — no proprietary abstractions, code is portable
- First-class middleware ecosystem: CORS, auth, logging, rate limiting,
@hono/zod-validator - Mounts at
/api/*inside the TanStack Start server — one container, one deploy @hono/zod-openapigives OpenAPI specs and generated clients when we need them- Extracts cleanly to a standalone service later if a product ever outgrows the monolith
- Tiny (~14 kB), fast, actively maintained
Cons
- A second way to do server work — requires a written convention (see Decision) so server functions and Hono routes don’t blur
- Smaller community than Express (though growing fast)
4. Express / Fastify
Section titled “4. Express / Fastify”The Node incumbents.
Pros
- Express: everyone knows it; Express 5 (npm
latestsince 2025-03) finally handles async errors. Fastify: fast, schema-validated, mature plugin system
Cons
- Express: dated middleware API, types via
@types/expressrather than inference, Node-only - Fastify: solid, but Node-only and pre-dates web-standard Request/Response — Hono gives the same performance class with better portability and TS inference
- Neither mounts as naturally into a TanStack Start server handler
5. tRPC
Section titled “5. tRPC”Typed RPC layer.
Pros
- Excellent TS DX for internal APIs
Cons
- Overlaps almost entirely with server functions we already have
- Not a REST contract — poor fit for external clients, webhooks, OpenAPI
Decision
Section titled “Decision”Hono is the company-standard API layer, mounted at /api/* inside the TanStack Start server (same Docker container).
Convention — two server patterns, one rule:
- Server functions → UI-internal data loading and mutations consumed only by our own React app
- Hono routes (
/api/*) → anything consumed as an HTTP API: external clients, webhooks, mobile, partners, and anything that needs the middleware pipeline
The deciding factors over Start’s own server routes: web-standard APIs, the HTTP middleware ecosystem (auth lands here, ADR-0009), Zod integration via @hono/zod-validator, OpenAPI potential via @hono/zod-openapi, and a clean extraction path to a standalone service without rewriting.
Consequences
Section titled “Consequences”Easier — what this unlocks for later ADRs:
- Rate limiting: applied before Hono, in Start’s global request middleware, so one limiter covers
/api/*, server functions, and SSR (ADR-0010) - Auth: Hono middleware guards API routes independently of UI session flow
- OpenAPI:
@hono/zod-openapiturns our Zod schemas into a spec and generated clients - Observability: one middleware point for request logging/metrics on the API surface
Harder — what we now own:
- Two server patterns in every app (server functions + Hono routes). The convention above must be followed; code review enforces it
- API versioning (
/api/v1/...) is our discipline, not a framework feature
Given up: the “just server functions” single-pattern simplicity, and tRPC-style RPC purity for the external API. Accepted — a standard HTTP contract for the outside world is worth one extra framework.

