Skip to content

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

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 → Response handlers on file routes (server.handlers). Real HTTP endpoints, but no schema-validated route definitions and no OpenAPI.
  • Middleware — createMiddleware, plus global request middleware in src/start.ts that 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)

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

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

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-openapi gives 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)

The Node incumbents.

Pros

  • Express: everyone knows it; Express 5 (npm latest since 2025-03) finally handles async errors. Fastify: fast, schema-validated, mature plugin system

Cons

  • Express: dated middleware API, types via @types/express rather 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

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

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.

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-openapi turns 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.