Choosing between tRPC and REST is the first architectural decision that actually shapes your entire TypeScript codebase, and most teams pick wrong by defaulting to what they already know. The real question isn't which is "better" — it's whether you're optimizing for type safety or for universal client compatibility.
The Core Decision
The tRPC vs REST debate comes down to one fundamental trade-off: tRPC gives you end-to-end type safety with zero code generation, while REST gives you a language-agnostic API that any client can consume. In my experience, teams that pick tRPC for public APIs or pick REST for internal monoliths both end up fighting their tools. Here's exactly how to decide.
tRPC vs REST: The Key Differences
The difference isn't just about typing — it's about the entire contract between client and server.
tRPC treats your server functions as if they were local function calls. The client imports a typed router, and TypeScript infers every input and output. No schemas, no generators, no manual type definitions. If you change a server function's return type, the client breaks at compile time — not at 3 AM in production.
REST treats your API as a set of resources with explicit HTTP verbs and status codes. The contract lives in documentation (OpenAPI, Postman collections) or in shared type packages that drift out of sync. You get caching, HTTP semantics, and universal accessibility — but you pay for it with boilerplate and manual type maintenance.
The other difference is error handling. tRPC throws typed errors that the client catches directly. REST requires you to interpret HTTP status codes and parse error bodies — a whole layer of mapping logic that tRPC eliminates.
When to Use tRPC
Use tRPC when you control both ends of the wire — a Next.js or Remix app with a dedicated backend, an internal admin dashboard, or a B2B tool where your customers use your SDK.
Here's the concrete difference in developer experience:
// tRPC — server side
export const appRouter = router({
getUser: procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input.id } });
return user; // TypeScript knows this is User | null
}),
});
// tRPC — client side
const user = await trpc.getUser.query({ id: "123" });
// user is typed as User | null — no manual types, no casting
If you change the return type on the server, the client breaks immediately. That's the killer feature.
When to Use REST
Use REST when you have external consumers — third-party integrations, mobile apps written in Swift or Kotlin, or any client that doesn't speak TypeScript. Also use it when you need HTTP-level features like caching, conditional requests, or rate limiting at the proxy level.
// REST — server side
app.get("/api/users/:id", async (req, res) => {
const user = await db.user.findUnique({ where: { id: req.params.id } });
res.json(user);
});
// REST — client side (any language)
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
// No type safety — you cast it yourself or use a generated client
REST also wins when your API needs to outlive the current team — because the contract is explicit and documented, not implicit in TypeScript types.
tRPC or REST: Which One Should You Pick?
If you're building a full-stack TypeScript app with no external consumers, pick tRPC. It eliminates an entire class of bugs and speeds up development dramatically.
If you're building a public API, a microservice boundary, or anything with non-TypeScript clients, pick REST. The type safety you lose is worth the universal compatibility you gain.
The deciding factor is whether every client that will ever call your API is written in TypeScript and maintained by your team. If yes — tRPC. If no — REST.
My Take
I've shipped production apps with both, and my answer is clear: start with tRPC for anything internal, and only reach for REST when you have a concrete need for external clients or HTTP caching. The type safety alone saves more engineering hours than REST's familiarity ever will. You can always wrap a tRPC router with a REST adapter later — the reverse is a painful migration.
The one thing that makes this decision obvious: if you can't name a single non-TypeScript client that will consume your API today, you're building REST for a problem you don't have. Use tRPC and thank yourself later.