API design: REST, GraphQL or tRPC?
By Ruben Bijker · Published · 6 min read
Pick REST as the default, GraphQL when clients need flexible data shapes, and tRPC when front end and back end live in one TypeScript codebase. How well you design the API usually matters more than which style you choose.
The three styles
| Style | Idea | Strong at | Watch out for | | --- | --- | --- | --- | | REST | Resources and HTTP verbs | Simplicity, HTTP caching, tooling | Over- and under-fetching, many endpoints | | GraphQL | One endpoint, clients ask for fields | Flexible clients, typed schema | Caching, query cost control, more server complexity | | tRPC | Call server functions with shared types | End-to-end type safety, zero schema duplication | Tied to TypeScript; not a public API format |
Design habits that matter in any style
- Consistent naming and structure. Plural nouns, predictable paths, the same field names everywhere.
- Clear error responses. Use proper status codes and a stable error shape with a machine-readable code and a human message.
- Pagination from day one. Cursor-based pagination scales better than offset for growing data.
- Idempotency. Make retries safe for payments, orders and other side effects with idempotency keys.
- Versioning strategy. Prefer additive changes; when breaking, version explicitly and deprecate with notice.
- Validation at the boundary. Validate every input with a schema (for example Zod) and never trust the client.
- Authentication and authorisation. Separate who you are from what you may do, and check permissions on every resource.
- Rate limiting. Protect the API and your costs from abuse and runaway clients.
Documentation and contracts
An API without documentation is a guess. Describe REST APIs with OpenAPI and generate clients from the spec. GraphQL has introspection built in. With tRPC, the types are the contract. Whatever you use, keep the contract in the repository and test against it.
Real-time and background work
For live updates, consider Server-Sent Events for one-way streams and WebSockets for two-way messaging. For slow work, return quickly and process in a queue, then expose job status or push a notification when it finishes.
A pragmatic choice
- Public or third-party API: REST with OpenAPI.
- Many client types with differing needs: GraphQL.
- One TypeScript app, one team: tRPC or server actions.
Related: choosing a stack and TypeScript in web projects.
Frequently asked questions
- Is REST outdated?
- No. REST over HTTP is simple, cacheable and understood by every tool and developer. It remains a sound default for most APIs.
- When is GraphQL worth the extra complexity?
- When many different clients need different shapes of the same data, or when over-fetching and many round trips are a real problem.
- Can tRPC be used with a non-TypeScript back end?
- Not in a useful way. tRPC relies on shared TypeScript types, so it fits full-stack TypeScript projects.
Related service: Software engineer for custom web applications
Have a project in mind?
Tell me briefly what you need. You get an answer from me, not from a sales team.
Get in touchKeep reading
- TypeScript in web projects: practices that pay off
How to use TypeScript well in web projects: strict mode, runtime validation, shared types, testing and the pitfalls that undo the benefits.
- SSR, SSG, ISR and CSR: choosing a rendering strategy
What server-side rendering, static generation, incremental regeneration and client-side rendering actually do, and how to pick the right one per page.