Alex Chen
Back to blog

Building Type-Safe APIs with tRPC and TanStack Query

·Alex Chen
TypeScripttRPCTanStack QueryAPI Design

If you've ever spent an afternoon debugging a frontend bug caused by an API response shape you didn't know had changed, you've already felt the pain that tRPC was built to solve.

The Problem

In most full-stack apps, the API boundary is a trust cliff: your TypeScript types on the server and the types on the client are defined separately. They drift apart, and you only find out at runtime — or worse, after a production incident.

OpenAPI generators and code generation tools help, but they add friction and are rarely kept perfectly in sync.

Enter tRPC

tRPC lets you call server functions from the client as if they were local functions — with full TypeScript inference end-to-end. No code generation, no schemas to keep in sync, no drift.

// server/routers/user.ts
export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return db.user.findUnique({ where: { id: input.id } })
    }),
})

// client component
const { data: user } = trpc.user.getById.useQuery({ id: userId })
// `user` is fully typed — no casting, no guessing

Pairing with TanStack Query

tRPC's React adapter is built on TanStack Query, which means you get all the caching, background refetching, and optimistic updates for free. The combination is genuinely ergonomic: mutations, infinite queries, and suspense mode all work exactly as you'd expect.

When Not to Use It

tRPC is a great fit for monorepos where the client and server live together. If you're building a public API consumed by third parties, REST or GraphQL is still the better choice.

For internal full-stack apps, though, it's hard to beat the developer experience.