tRPC v11: A Fully Type-Safe API Without Code Generation
Zero GraphQL schema, zero codegen, zero runtime overhead: how tRPC v11 changes the way you build APIs for Next.js SaaS products.
The problem with REST and GraphQL
REST: you write your API, then you write the client-side types. By hand. On every change. And when the types drift... a silent bug in production.
GraphQL: type safety, yes. But at the cost of a schema to maintain, a codegen step to run, and a runtime that parses every request.
tRPC: you write your API in TypeScript, and the client gets the types automatically. No codegen, no schema, no runtime overhead.
Setup in a Next.js SaaS
The tRPC router
// src/server/api/routers/invoice.ts
import { z } from "zod";
import { createTRPCRouter, staffProcedure } from "~/server/api/trpc";
export const invoiceRouter = createTRPCRouter({
getAll: staffProcedure.query(async ({ ctx }) => {
return ctx.orgDb.invoice.findMany({
orderBy: { createdAt: "desc" },
include: { client: true },
});
}),
create: staffProcedure
.input(
z.object({
clientId: z.string(),
items: z.array(
z.object({
description: z.string(),
quantity: z.number().positive(),
unitPrice: z.number().positive(),
}),
),
}),
)
.mutation(async ({ ctx, input }) => {
return ctx.db.invoice.create({
data: {
organizationId: ctx.session.user.organizationId,
clientId: input.clientId,
items: { create: input.items },
},
});
}),
});On the client side: zero configuration
"use client";
import { api } from "~/trpc/react";
export function InvoiceList() {
const { data, isLoading } = api.invoice.getAll.useQuery();
if (isLoading) return <Skeleton />;
return (
<div>
{data?.map((invoice) => (
// invoice is typed automatically: full autocomplete
<InvoiceCard key={invoice.id} invoice={invoice} />
))}
</div>
);
}Change the return type on the server → TypeScript flags the client-side errors immediately. Before you even run the app.
The procedure hierarchy
tRPC v11 lets you chain middlewares to build access levels:
// From least to most restrictive
export const publicProcedure = t.procedure;
export const protectedProcedure = publicProcedure.use(enforceAuth);
export const staffProcedure = protectedProcedure.use(enforceStaffRole);
export const adminProcedure = protectedProcedure.use(enforceAdminRole);
// Granular permission
export const requirePermission = (perm: string) =>
staffProcedure.use(({ ctx, next }) => {
if (!hasPermission(ctx.session.user.role, perm)) {
throw new TRPCError({ code: "FORBIDDEN" });
}
return next({ ctx });
});Each router picks the right level. A public query? publicProcedure. An admin CRUD? requirePermission("invoices:write").
Validation with Zod
tRPC integrates natively with Zod for input validation:
.input(
z.object({
search: z.string().optional(),
page: z.number().int().positive().default(1),
perPage: z.number().int().min(1).max(100).default(20),
})
)The type of input inside your handler is automatically inferred from the Zod schema. One single place for both validation AND types.
Optimistic mutations
const utils = api.useUtils();
const createInvoice = api.invoice.create.useMutation({
onSuccess: () => {
// Invalidate the cache to refetch
utils.invoice.getAll.invalidate();
},
});Why not GraphQL?
| Criterion | tRPC | GraphQL |
|---|---|---|
| Codegen | No | Yes |
| Runtime overhead | Zero | Parsing + resolution |
| Setup | 10 min | 30 min + tooling |
| Type safety | Full | Full (with codegen) |
| Best fit | SaaS monorepo | Public, multi-client API |
Simple rule
If your API is only ever consumed by your own frontend (a SaaS), pick tRPC. If you have external clients (a third-party mobile app, partners), pick GraphQL.
Conclusion
tRPC v11 eliminates a whole category of bugs: types drifting apart between client and server. In a SaaS where velocity matters, that's a massive productivity gain.
Go further
Related articles
Mistral AI in a B2B SaaS: 5 Concrete TypeScript Use Cases
Integrating a European AI provider into your SaaS: data extraction, summaries, email classification, content generation, and semantic search.
ReadTesting a Multi-Tenant SaaS: 7 Essential Vitest Patterns
From tenant isolation to RBAC permission checks, the test patterns that make a B2B SaaS genuinely reliable. With Vitest, tRPC, and Prisma.
ReadMulti-Tenant Isolation with Prisma and tRPC: What the Automatic Filter Covers, and What Doesn't
Isolate each organization's data in Next.js with Prisma $extends and tRPC: what the automatic filter covers, and what stays explicit.
ReadReady to launch your SaaS?
HeartCo Starter includes everything you need: auth, payments, AI, mobile, audited security. Starting at $219.