Appearance
004 — tRPC API (not GraphQL, server functions, or subscriptions)
Context
One first-party web app talks to one Worker. Both sides are TypeScript. Procedures are already namespaced (auth.*, domains.*) with Zod inputs and stable appCode errors (API reference).
Decision
Use tRPC (fetch adapter) on the Worker and @trpc/client + @trpc/react-query on the web. Send cookies with credentials: 'include'. Shared error codes live in @send-again/shared.
Why not GraphQL
- GraphQL shines with many clients, flexible field selection, and a published schema. This SPA almost always needs full
DomainDetail/ list shapes — not field shopping. - Would add SDL (or code-first GraphQL), resolvers, and often codegen in addition to Zod already used for inputs — duplicate contract surface for little gain.
- Subscriptions/realtime are unused; cron stays on the Worker, not in the API layer.
Why not server functions (Next / Remix / TanStack Start-style)
- Server functions couple UI and backend into one framework deploy. This prototype deliberately splits Vite SPA + Worker (ADR 005, ADR 001) so cron, D1, and API share the Worker without a full-stack meta-framework.
- Colocated server actions blur the domain service boundary the challenge is meant to showcase; tRPC procedures stay thin adapters over services/repos.
- Server functions often assume Node or framework-specific runtimes; Workers + Wrangler already define the server target.
Why not hand-rolled REST + OpenAPI
- Would re-declare the same input/output types the SPA needs, or maintain OpenAPI as a third artifact. Fine for public/multi-language APIs — not needed for one TypeScript client.
Realtime: client polling, not tRPC subscriptions
- DNS verification is slow (server poll interval / cron ~5 min). WebSocket push does not speed up DNS and adds Workers realtime complexity (hibernation/Durable Objects, reconnect, cookie auth on long-lived connections).
- Verification/claim UI should use short client polling of existing
domains.verify/domains.getwhile the screen is open (e.g. every 15–30s or on focus), stopping on terminal status or navigation. The server still enforcesDNS_POLL_INTERVAL_MS, so extra client ticks mostly return cached state. - tRPC subscriptions are deferred; revisit only for true multi-user live collaboration or instant cross-tab alerts beyond email + next fetch.
Consequences
- Procedure renames and breaking input/output changes show up at compile time in the SPA.
- Docs still describe the HTTP shape for humans.
- A public third-party API, multi-language clients, or highly flexible querying would push toward REST/OpenAPI or GraphQL later — out of scope for this prototype.