patterntypescriptTip
tRPC End-to-End Type Safety: Share types between server and client without codegen
Viewed 0 times
trpctype-safetyfullstacktypescriptinferencerouterprocedure
Problem
In REST APIs, the server and client type systems are disconnected. Runtime errors occur when the server changes a response shape that the client still types incorrectly. Codegen solves this but adds build complexity.
Solution
Use tRPC to define procedures on the server and infer their types directly on the client. The router type is shared as a TypeScript type (not at runtime), giving end-to-end type safety with zero codegen.
Why
tRPC eliminates an entire class of type drift bugs. Since types are inferred from the server's actual implementation, refactoring the server immediately shows type errors in client code.
Gotchas
- tRPC requires both client and server to be TypeScript — it is not suitable for public APIs consumed by non-TypeScript clients.
- The router type import must not import server-only modules (like DB clients) on the client — use type-only imports.
- tRPC does not replace OpenAPI for documentation purposes; use trpc-openapi if you need both.
Code Snippets
Minimal tRPC server and client setup
// server.ts
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.users.findById(input.id) // return type inferred
})
})
export type AppRouter = typeof appRouter
// client.ts
import type { AppRouter } from './server'
import { createTRPCProxyClient } from '@trpc/client'
const client = createTRPCProxyClient<AppRouter>({ /* config */ })
const user = await client.getUser.query({ id: '123' })
// user is fully typed — no codegen neededRevisions (0)
No revisions yet.