API Design Patterns That Scale: REST, tRPC, and When to Use Each
After building APIs in REST, GraphQL, and tRPC across many different products, I have a clear view of when each approach wins and where each one fails.
Every API is a contract between two systems. The choice of REST vs tRPC vs GraphQL is less about technology preference and more about who the other system is, how often the contract changes, and how much type safety you need.
REST: still the right default
REST is the right default for public APIs, mobile apps, and anything consumed by systems you do not control. Its strengths are:
- Every HTTP client speaks it natively
- Caching is handled by the infrastructure layer
- Easily documented with OpenAPI
- No shared codebase required between client and server
REST fails when your TypeScript frontend team is constantly fighting with the backend team over what shape a response should be, or when you are fetching the same resource with wildly different field requirements on every screen.
tRPC: the best TypeScript DX
If you control both the client and server (a Next.js full-stack app, an Electron app, a React Native app with a Node backend), tRPC is transformative. You define a function on the server, call it on the client, and TypeScript knows the types of both the input and output without generating any code.
// Server
const appRouter = router({
user: {
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => db.user.findUnique({ where: { id: input.id } })),
},
});
// Client — fully typed, no codegen
const user = await trpc.user.byId.query({ id: '123' });
// user.name, user.email are all typed correctlyThe decision tree
Public API consumed by external clients? Use REST. Need real-time subscriptions at scale? Use WebSockets or SSE. TypeScript monorepo where you control client and server? Use tRPC. Complex, multi-team product with wildly different data needs per view? Consider GraphQL.
The pattern I use most
For most Next.js projects: tRPC for internal mutations and queries, REST endpoints for webhooks and third-party integrations. This covers 90% of real-world needs without GraphQL's overhead.
The best API is the simplest one that solves your actual constraints. Start with REST, add tRPC when you feel the type-safety pain, and reach for GraphQL only when you have genuinely outgrown the alternatives.