Why I switched from REST to tRPC on my side projects
Every side project starts the same way: a handful of REST endpoints, a fetch wrapper, and a type file you promise to keep in sync. Six weeks later the type file is wrong, a field got renamed on the backend, and you find out in production. tRPC fixes the exact part of that story I was tired of.
The waterfall problem
A single user profile page used to cost me three sequential requests: fetch the user, fetch their posts, fetch comments on the first post. Each one waited on the last, and each one needed its own loading state.
GET /api/user/1
GET /api/user/1/posts
GET /api/user/1/posts/9/comments
With tRPC, that becomes one typed procedure call. The client gets full autocomplete on the response shape, and if the backend changes a field name, the build fails instead of the page.
const profile = await trpc.user.withPosts.query(1);
// profile is fully typed, no .d.ts to maintain
The type file you promise to keep in sync is the one that's always six weeks out of date.
Where it doesn't help
If you need a public API that other teams or third parties consume, tRPC isn't the right tool — it assumes a TypeScript client on the other end. I still reach for REST or GraphQL for anything crossing a real API boundary.
What I'd tell past me
- Start with tRPC only on internal, full-stack TypeScript projects.
- Don't migrate an existing REST API just to try it — build the next one this way instead.
- Pair it with Zod for input validation; you get runtime safety for free.
Six months in, the thing I notice most isn't the saved boilerplate — it's how rarely a backend change breaks the frontend without me knowing at build time.