Every REST API I've ever built turns into a maintenance hell. You write your backend endpoints, define TypeScript interfaces for the responses, and pray they stay in sync. They never fucking do.
I've debugged countless production bugs where the frontend assumed a property existed that the backend renamed or removed. "Cannot read property 'email' of undefined" became the bane of my existence. The worst part? These errors only surface when users hit specific code paths in production.
GraphQL Made It Worse
GraphQL promised to fix this with schemas, but now I'm maintaining GraphQL schemas, resolvers, TypeScript types, AND making sure they all match. Plus GraphQL is massive overkill for internal CRUD apps - I don't need to query exactly 3 specific fields from a user object when I could just return the whole damn thing.
I spent more time fighting with Apollo codegen than actually building features. The schema-first approach sounds great until you realize you're writing the same data structure in 4 different places and hoping they stay synchronized.
tRPC: Your Functions Are Your API
tRPC ditches schemas entirely. You write TypeScript functions on your server and they become your API:
const appRouter = router({
getUser: publicProcedure
.input(z.string())
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input } })
}),
})
On the frontend, you call it like any other function:
const user = await trpc.getUser.query('user-123')
// TypeScript knows the exact return type automatically
That's it. No schema bullshit, no codegen breaking your build, no crossing your fingers that your types match reality.
The Moment It Clicked
I was refactoring a user model and changed username
to handle
in the database. In the old world, I'd grep through the codebase, find all the places that referenced it, and pray I didn't miss any.
With tRPC, TypeScript immediately lit up 23 locations across my React app that broke. Every single place. No guessing, no hunting, no production surprises. I fixed them all in 10 minutes and shipped with confidence.
Why This Matters for Real Projects
Most apps don't need GraphQL's complexity. You're building CRUD operations, not Facebook's social graph. tRPC gives you the type safety without the overhead.
The GitHub repo has 38k+ stars and the npm downloads keep growing. It's not hype - teams are shipping this in production because it actually solves the type safety problem without creating new ones.
Request batching happens automatically. Multiple queries get bundled into one HTTP request. Caching works with TanStack Query. It's boring technology that just works, which is exactly what you want from your API layer.