GraphQL needs code generation. REST needs manual types. tRPC gives you end-to-end type safety by simply importing your server types into your client. Zero code gen.
What is tRPC?
tRPC lets you build fully type-safe APIs without schemas, code generation, or runtime validation at the boundary. Your TypeScript types flow from server to client automatically.
What's New in tRPC v11
1. Simplified Router Definition
// server/router.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 await db.users.findUnique({ where: { id: input.id } });
}),
createUser: t.procedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(async ({ input }) => {
return await db.users.create({ data: input });
}),
listPosts: t.procedure
.input(z.object({ limit: z.number().default(10), cursor: z.string().optional() }))
.query(async ({ input }) => {
const posts = await db.posts.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
});
return {
items: posts.slice(0, input.limit),
nextCursor: posts[input.limit]?.id,
};
}),
});
export type AppRouter = typeof appRouter;
2. Client With Full Autocomplete
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/router';
const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/api/trpc' })],
});
// Full type inference — no code generation!
const user = await trpc.getUser.query({ id: '123' });
// user is typed as { id: string, name: string, email: string }
const newUser = await trpc.createUser.mutate({ name: 'Alice', email: 'a@b.com' });
3. React Query Integration
import { trpc } from './utils/trpc';
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading } = trpc.getUser.useQuery({ id: userId });
if (isLoading) return <Skeleton />;
return <h1>{user?.name}</h1>;
}
function CreateUserForm() {
const utils = trpc.useUtils();
const mutation = trpc.createUser.useMutation({
onSuccess: () => utils.listUsers.invalidate(),
});
return (
<form onSubmit={(e) => {
e.preventDefault();
mutation.mutate({ name: 'Bob', email: 'bob@example.com' });
}}>
<button>Create User</button>
</form>
);
}
4. Subscriptions (Real-Time)
// Server
const appRouter = t.router({
onNewMessage: t.procedure.subscription(async function* ({ ctx }) {
for await (const message of ctx.messageStream) {
yield message;
}
}),
});
// Client
const subscription = trpc.onNewMessage.subscribe(undefined, {
onData: (message) => console.log('New message:', message),
});
5. Middleware
const isAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { user: ctx.user } });
});
const protectedProcedure = t.procedure.use(isAuthed);
const appRouter = t.router({
secretData: protectedProcedure.query(async ({ ctx }) => {
return await getSecretData(ctx.user.id);
}),
});
tRPC vs REST vs GraphQL
| tRPC | REST | GraphQL | |
|---|---|---|---|
| Type safety | Automatic | Manual | Code gen required |
| Schema | None (inferred) | OpenAPI (optional) | SDL required |
| Code gen | None | Optional | Required |
| Bundle size | Minimal | Minimal | Apollo (~40KB) |
| Learning curve | Low (just TS) | Low | Moderate |
| Use case | TS fullstack | Any | Multi-client |
Getting Started
npm install @trpc/server @trpc/client @trpc/react-query
The Bottom Line
tRPC is the fastest way to build a type-safe API in TypeScript. No schemas, no code generation — just import your server types and get full autocomplete on the client.
Need data solutions? I build scraping tools. Check my Apify actors or email spinov001@gmail.com.
Top comments (0)