tRPC: End-to-End Type Safety Without REST or GraphQL
tRPC lets you call server functions from your client as if they were local functions — fully typed, no code generation, no schema files.
How It Works
Define procedures on the server:
import { z } from 'zod';
import { router, publicProcedure } from './trpc';
export const appRouter = router({
users: router({
list: publicProcedure
.input(z.object({ limit: z.number().default(10) }))
.query(async ({ input }) => db.users.findMany({ take: input.limit })),
create: publicProcedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(async ({ input }) => db.users.create({ data: input })),
}),
});
export type AppRouter = typeof appRouter;
Call them from the client with full autocomplete:
const { data: users } = trpc.users.list.useQuery({ limit: 20 });
const createUser = trpc.users.create.useMutation();
No REST endpoints. No fetch calls. No any types. If the server changes the response shape, TypeScript errors immediately in the client.
Protected Procedures
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { session: ctx.session } });
});
export const protectedProcedure = t.procedure.use(isAuthed);
When NOT to Use tRPC
tRPC requires both client and server to be TypeScript in the same monorepo. If external clients (mobile apps, third-party integrations) need to consume your API, use REST or GraphQL.
tRPC + Next.js + Prisma + NextAuth + Stripe — this exact stack is pre-wired in the AI SaaS Starter Kit. Skip the plumbing and start building.
Top comments (0)