The API wars have quieted down. For the past 5 years, the question was: "Do we go REST or GraphQL?" Startups picked GraphQL and regretted the operational overhead. Teams picked REST and regretted the tight coupling. Then tRPC showed up and said: "What if type safety is free and you don't need a schema language?" It's 2026 now. The conversation has shifted. REST still dominates public APIs. GraphQL owns enterprises with 50+ downstream clients. tRPC has become the default inside full-stack TypeScript products — Velocity SaaS included. Here's when to use each, and why the decision is mostly already made for you.
What They Are (30 Seconds)
REST: HTTP verbs (GET, POST, PATCH, DELETE) map to resource operations. Client specifies what it wants via URL and query params. Server returns what it fetches. Simple. Predictable. Every tool understands it.
GraphQL: Client sends a query describing exactly what fields it needs. Server parses the query, fetches only those fields, returns them. Zero over-fetching. Requires a schema language, query planner, N+1 query prevention, and operational overhead.
tRPC: TypeScript procedures exposed over HTTP. Client calls them like async functions. Type safety is automatic — the client knows what the server returns because they're both TypeScript. No schema language. No code generation. RPC-style, but better.
The Comparison: DX, Type Safety, Public API Fit
Developer Experience
REST: Clear, standardized. Every API works the same way. Documentation is easy. New team members get it immediately. The catch: tight coupling. You build endpoints to match your UI, then regret it when the next feature asks for different data.
GraphQL: Powerful. One endpoint. Query exactly what you need. Documentation is auto-generated. The catch: developers spend more time writing queries, debugging N+1 problems, and learning the ecosystem (Apollo, Relay, middleware). Not simple.
tRPC: RPC-style procedures. Define a procedure, call it from the client. Feels like calling a local function but goes over HTTP. IntelliSense works. The catch: only works for TypeScript clients. Not suitable for public APIs consumed by JS, Python, or mobile apps.
Type Safety
REST: None by default. You document "this endpoint returns { id: string, name: string }" and hope the client matches. Code generation tools (OpenAPI, JSON Schema) can help, but you're still generating code from specs.
GraphQL: Strong. The schema is the source of truth. Clients generate types from it. Full end-to-end type safety, but the schema must be maintained and the generation pipeline must run before each deploy.
tRPC: Perfect. You define the procedure's input and output as TypeScript types. The client automatically infers them. No code generation, no schema language, no drift. What you type is what you get.
Public API Fit
REST: Gold standard. Every language, every platform, every API client library understands HTTP verbs. Documentation is intuitive. Your Python, JavaScript, and Rust users all see the same interface.
GraphQL: Strong contender. Clients can query exactly what they need — huge win for mobile or bandwidth-constrained environments. The ecosystem is mature. Major APIs (GitHub, Shopify) use it.
tRPC: Not suitable for public APIs. It requires TypeScript on the client. You're not going to ask your Python user to call a tRPC endpoint. Not happening.
Decision Tree: Which One?
You're building a SaaS product. Both frontend and backend are yours. Single TypeScript team.
Pick tRPC. Type safety is free. You avoid schema language syntax. Deployment is simpler. Your team ships faster. Internal use only, so the TypeScript-only constraint doesn't matter. Velocity X products use tRPC.
You're building a public API. Customers will consume it from Rust, Python, Go, mobile apps.
Pick REST. Broad compatibility. Documentation is intuitive. Every language has an HTTP client. You'll thank yourself when a customer asks "Can we call this from our Java backend?" and the answer is "Of course."
You're an enterprise with 50+ downstream clients, all demanding different data shapes.
Pick GraphQL. One endpoint. Clients query exactly what they need. You avoid building 50 custom endpoints. The operational overhead is worth it at scale. Stripe, GitHub, and AWS-scale companies use it.
You're a startup with no decision yet. You're trying to move fast.
Start with REST if you might have a public API later. Start with tRPC if it's always going to be your own frontend.** Change your mind later if needed. REST-to-tRPC is easy (both are HTTP under the hood). GraphQL is harder to add retroactively.
tRPC Setup: The Essentials
Here's how fast it is. Install @trpc/server, @trpc/client, and @trpc/react-query:
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const router = t.router({
// query: GET-like, cache-safe
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return { id: input.id, name: 'Alice', email: 'alice@example.com' };
}),
// mutation: POST/PATCH-like, side effects
createPost: t.procedure
.input(z.object({ title: z.string(), content: z.string() }))
.mutation(async ({ input }) => {
// insert into db
return { id: 'post-123', ...input };
}),
});
export type AppRouter = typeof router;
On the client, you get full type safety for free:
// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCReact<AppRouter>();
Now use it in a component. IntelliSense shows you what the server returns:
// Component.tsx
import { trpc } from './trpc';
export function UserCard() {
const { data, isLoading } = trpc.getUser.useQuery({ id: 'user-1' });
if (isLoading) return <p>Loading...</p>;
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
</div>
);
}
That's it. TypeScript knows data.email exists. If the server returns phone instead, you get a compile error. The type safety is automatic — no code generation, no schema language, no waiting for a build step.
Six FAQs
Can I use tRPC with Astro or static sites?
tRPC works best with React/Next.js where you have persistent client state. Astro's island-based architecture makes it harder — you'd need to wire tRPC to each island separately. Possible, but awkward. For Astro, REST or custom fetch functions are cleaner.
GraphQL has a learning curve. Is it worth it for a small team?
No. Small teams ship fastest with REST or tRPC. GraphQL pays off when you have multiple clients with different data needs. If it's just you building an internal API, the overhead outweighs the benefit.
tRPC has no public API story. What if we grow and need to expose our API?
Wrap your tRPC router in REST endpoints. t.procedure is just a function; you can call it from a REST handler and return the result. You'll duplicate some logic, but it's straightforward. Better yet, plan for REST from the start if you think you might go public.
How do I handle caching with tRPC?
tRPC uses React Query under the hood. React Query has built-in caching and stale-while-revalidate semantics. Configure cache time on the procedure or in the query hook. For REST, you'd use HTTP cache headers; tRPC lets you control caching from the client or server.
GraphQL and REST can both handle authentication. Does tRPC?
Yes. Pass the auth token in the request header (same as REST). tRPC middleware can check it before the procedure runs. If the token is invalid, throw an error — the client gets a type-safe error response.
Can I use tRPC alongside REST?
Yes. You might expose tRPC to your frontend and REST to your public API. Two systems, same database, same business logic. Keep them in sync by sharing Zod schemas (like we covered in the Zod post). Parse the request with the same schema in both tRPC and REST handlers.
The Bottom Line
REST is the safest choice for public APIs — it's simple, language-agnostic, and understood everywhere. GraphQL is the right call if you have dozens of clients with different data requirements and the operational overhead doesn't scare you. tRPC is the modern default for full-stack TypeScript teams building internal SaaS — no schema language, no code generation, type safety is automatic. If you're writing a Velocity product, use tRPC. If you're shipping a customer-facing API, use REST. If you're Facebook scale, GraphQL. The decision is mostly about constraints, not hype. Build accordingly. Ready to architect a type-safe full-stack app? Check Aidxn Design pricing for full-stack partnerships.