# Why We Chose tRPC Over REST (And When REST Still Wins)
Eighteen months ago, we migrated our internal API from REST to tRPC. The decision was controversial on our team. The result has been overwhelmingly positive — with a few caveats that are worth discussing honestly.
## The Problem We Were Solving
Our REST API had grown to 127 endpoints. Each endpoint had a request type, a response type, and validation logic — all maintained separately from the frontend code that consumed them. When someone changed a response field on the backend, the frontend broke at runtime. Not at build time. Not in the editor. At runtime, usually in production.
We had OpenAPI specs, we had code generation, we had shared type packages. It still happened roughly once per sprint. The gap between "the API contract" and "what the code actually does" was persistent and expensive.
## What tRPC Actually Is
For anyone unfamiliar: tRPC lets you define your backend API procedures in TypeScript, and the frontend automatically knows the exact types — input shapes, output shapes, error types — without any code generation step.
You write a procedure on the backend:
The frontend calls it and gets full TypeScript autocomplete and type checking. If the backend changes a field from "string" to "number", the frontend shows a type error immediately in the editor.
No OpenAPI spec. No code generation. No runtime surprises.
## What We Love After 18 Months
### Zero API Bugs from Type Mismatches
In 18 months, we've had exactly zero production bugs caused by the frontend sending the wrong data shape or misinterpreting a response. Zero. Compare that to roughly 6-8 per quarter with REST.
### Refactoring Is Safe
Renaming a field, changing a type, adding a required parameter — the TypeScript compiler catches every call site that needs updating. With REST, refactoring was a manual search-and-replace prayer.
### Faster Feature Development
Developers estimate a 20-30% speed improvement for features that involve API calls. No time spent writing request/response types. No time debugging serialization issues. No time updating OpenAPI specs.
### Input Validation Is Built In
tRPC integrates with Zod for input validation. The validation schema is the type definition. One source of truth, automatically enforced at the boundary.
## The Honest Downsides
### No External API (Yet)
tRPC is designed for TypeScript-to-TypeScript communication. If you need an API that non-TypeScript clients consume — mobile apps in Swift, partner integrations in Python, webhook consumers — tRPC doesn't serve them directly.
We still maintain REST endpoints for external integrations. This means we have two API layers: tRPC for our web app and REST for external consumers. It's not ideal, but the overlap is manageable — roughly 30 REST endpoints alongside 180+ tRPC procedures.
### Monorepo Coupling
tRPC's type sharing works best in a monorepo where backend and frontend share the same TypeScript project. If your frontend and backend are in separate repositories, the type sharing requires publishing packages or using git submodules — possible but less elegant.
### Learning Curve for Non-TypeScript Developers
Our team has backend developers with Java and Python backgrounds. tRPC's approach — where the "API" is just function calls with type inference — was conceptually unfamiliar. The learning curve was 2-3 weeks, not trivial but manageable.
### Debugging Network Requests
REST requests are human-readable in browser dev tools: clear URLs, JSON bodies, standard HTTP methods. tRPC requests go to a single endpoint with encoded procedure names and serialized inputs. Debugging is possible but less intuitive.
We mitigated this with the tRPC devtools panel and structured logging, but it's a real trade-off.
## When REST Still Wins
We haven't abandoned REST entirely, and we won't. REST is the right choice when:
**External parties consume your API.** REST is universal. Every programming language, every HTTP library, every automation tool speaks REST. tRPC is TypeScript-specific.
**You need public API documentation.** OpenAPI specs power interactive documentation, client SDK generation, and API gateways. tRPC doesn't produce OpenAPI specs natively (though adapters exist).
**Webhooks.** Incoming webhooks from external services (Stripe, Clerk, payment processors) are REST endpoints by nature.
**File uploads.** Multipart form data handling is more straightforward with REST endpoints than tRPC procedures. We use REST for all file upload endpoints.
## Our Architecture Now
The split is clear:
**tRPC (85% of API traffic):** All frontend-to-backend communication for our web application. Queries, mutations, subscriptions. Type-safe, fast to develop, reliable.
**REST (15% of API traffic):** Webhooks from external services. File upload endpoints. Health checks. Any endpoint that non-TypeScript clients need to consume.
Both layers share the same business logic and database access code. The API layer is just the interface — the implementation is shared.
## Performance Notes
tRPC adds negligible overhead compared to REST. Both ultimately use HTTP. The serialization/deserialization is the same (JSON). We measured response times before and after migration and found no meaningful difference.
tRPC's batching feature — combining multiple procedure calls into a single HTTP request — actually improved page load times for screens that fetch data from multiple endpoints. A page that previously made 8 REST calls now makes 1 batched tRPC request.
## Should You Switch?
**Yes, if:** Your frontend and backend are both TypeScript. You're in a monorepo (or willing to create one). API type mismatches cause real bugs. Developer velocity matters more than ecosystem breadth.
**No, if:** You need a public API as your primary product. Your frontend isn't TypeScript. Your team is small and REST works fine. You have strong OpenAPI tooling investment.
**Consider hybrid, if:** You need both internal type safety and external API access. Run tRPC for your app and REST for external integrations, sharing the same business logic layer.
## The Broader Point
tRPC isn't a technology decision. It's a philosophy decision: do you want an API contract that's enforced by documentation, or enforced by the compiler? We chose the compiler, and we haven't regretted it.