Warum tRPC 2026 der Standard für TypeScript-Projekte ist
REST-APIs mit manuell gepflegten Types auf Client und Server waren jahrelang der Status quo — mit dem unvermeidlichen Ergebnis: Typen divergieren, Bugs entstehen an der Grenze zwischen Frontend und Backend. GraphQL löste dieses Problem mit Code-Generation, schuf aber neue Komplexität. tRPC geht einen radikal anderen Weg: Der Router-Typ selbst ist das Schema. Kein Codegen-Schritt, keine .graphql-Dateien, keine OpenAPI-Spezifikation — TypeScript-Inferenz erledigt alles.
Claude Code hat sich 2026 als idealer Partner für tRPC-Projekte etabliert. Die KI versteht den gesamten Stack — von der Procedure-Definition auf dem Server bis zum useQuery-Hook im React-Component — und kann Typen, Validierung und Integration in einem Schritt aufsetzen.
tRPC Kernprinzip: Der TypeScript-Typ des Routers wird direkt auf den Client übertragen. Keine Build-Steps, keine generierten Dateien — nur TypeScript-Inferenz über Modulgrenzen hinweg.
1. Installation und Grundstruktur
Ein neues tRPC-Projekt startet mit wenigen Paketen. Claude Code legt die Verzeichnisstruktur automatisch an:
# Installation für Next.js Projekt
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next
npm install @tanstack/react-query zod
# Projektstruktur die Claude Code generiert
src/
server/
trpc.ts # tRPC Initialisierung + Context
routers/
_app.ts # Root Router
user.ts # User-Procedures
post.ts # Post-Procedures
utils/
trpc.ts # Client-seitiger tRPC-Hook
pages/
api/trpc/[trpc].ts # Next.js API-Handler
INIT tRPC Initialisierung: server/trpc.ts
Die Basis jedes tRPC-Projekts: Context-Definition und initTRPC-Konfiguration.
import { initTRPC, TRPCError } from '@trpc/server';
import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
import superjson from 'superjson';
// Context-Typ: wird in jeder Procedure verfügbar
export async function createTRPCContext(opts: CreateNextContextOptions) {
const { req, res } = opts;
// Session aus Cookie oder Header lesen
const session = await getServerSession(req, res);
return {
session,
db, // Prisma Client oder anderer ORM
req,
};
}
type Context = Awaited<ReturnType<typeof createTRPCContext>>;
// tRPC Initialisierung mit SuperJSON für Date/BigInt-Serialisierung
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});
// Exports: Router und Procedure-Bausteine
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
2. Router und Procedures definieren
Procedures sind die Kernbausteine von tRPC. Claude Code unterscheidet dabei zwischen query (lesend, wie GET) und mutation (schreibend, wie POST/PUT/DELETE).
PROC User Router mit Zod-Validierung
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
import { TRPCError } from '@trpc/server';
export const userRouter = router({
// Query: Einzelnen User abrufen
getById: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input, ctx }) => {
const user = await ctx.db.user.findUnique({
where: { id: input.id },
select: { id: true, name: true, email: true },
});
if (!user) throw new TRPCError({
code: 'NOT_FOUND',
message: 'User nicht gefunden',
});
return user;
}),
// Query: Liste mit Paginierung
list: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(20),
cursor: z.string().optional(),
}))
.query(async ({ input, ctx }) => {
const { limit, cursor } = input;
const users = await ctx.db.user.findMany({
take: limit + 1,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
});
let nextCursor: typeof cursor = undefined;
if (users.length > limit) {
const next = users.pop();
nextCursor = next?.id;
}
return { users, nextCursor };
}),
// Mutation: User erstellen
create: publicProcedure
.input(z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
role: z.enum(['USER', 'ADMIN']).default('USER'),
}))
.mutation(async ({ input, ctx }) => {
const exists = await ctx.db.user.findUnique({
where: { email: input.email },
});
if (exists) throw new TRPCError({
code: 'CONFLICT',
message: 'Email bereits vergeben',
});
return ctx.db.user.create({ data: input });
}),
});
3. Middleware und geschützte Procedures
Authentifizierungs-Middleware ist eines der mächtigsten Features von tRPC. Claude Code generiert typensichere Middleware-Ketten, die den Context anreichern.
MIDDLEWARE Auth-Middleware + Protected Procedure
import { middleware, publicProcedure, TRPCError } from './trpc';
// Auth-Middleware: prüft Session, wirft Fehler wenn nicht eingeloggt
const enforceUserIsAuthed = middleware(async ({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
// Session ist jetzt garantiert vorhanden — TypeScript weiß das!
session: { ...ctx.session, user: ctx.session.user },
},
});
});
// Rate-Limiting Middleware
const rateLimitMiddleware = middleware(async ({ ctx, next, path }) => {
const key = `rate-limit:${ctx.session?.user?.id ?? ctx.req.ip}:${path}`;
const requests = await redis.incr(key);
if (requests === 1) await redis.expire(key, 60);
if (requests > 60) throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: 'Zu viele Anfragen — bitte warte eine Minute',
});
return next();
});
// Protected Procedure: kombiniert Auth + Rate-Limiting
export const protectedProcedure = publicProcedure
.use(enforceUserIsAuthed)
.use(rateLimitMiddleware);
// Admin Procedure: zusätzliche Rollenprüfung
const enforceAdminRole = middleware(async ({ ctx, next }) => {
if (ctx.session?.user?.role !== 'ADMIN') {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return next();
});
export const adminProcedure = protectedProcedure.use(enforceAdminRole);
4. Root Router und Next.js API-Handler
ROUTER Root Router zusammenführen
// server/routers/_app.ts
import { router } from ''../trpc';
import { userRouter } from './user';
import { postRouter } from './post';
import { analyticsRouter } from './analytics';
export const appRouter = router({
user: userRouter,
post: postRouter,
analytics: analyticsRouter,
});
// Dieser Typ wird auf den Client exportiert — das ist tRPCs Magie
export type AppRouter = typeof appRouter;
// pages/api/trpc/[trpc].ts — Next.js Pages Router
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers/_app';
import { createTRPCContext } from '../../../server/trpc';
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError({ error, path }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
console.error(`[tRPC Error] ${path}:`, error);
}
},
});
5. React Query Integration: useQuery und useMutation
Der Client-seitige Teil von tRPC baut auf TanStack Query auf. Claude Code generiert den Provider-Setup und zeigt, wie Queries und Mutations vollständig typsicher aufgerufen werden.
CLIENT tRPC Client Setup und Provider
// utils/trpc.ts — Client-seitiger tRPC-Hook
import { createTRPCReact } from '@trpc/react-query';
import { type AppRouter } from '../server/routers/_app';
export const trpc = createTRPCReact<AppRouter>();
// _app.tsx — Provider-Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink, loggerLink } from '@trpc/client';
import superjson from 'superjson';
export default function App({ Component, pageProps }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
}));
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
loggerLink({ enabled: () => process.env.NODE_ENV === 'development' }),
httpBatchLink({
url: '/api/trpc',
transformer: superjson,
headers() {
return { 'x-trpc-source': 'react' };
},
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
</trpc.Provider>
);
}
HOOKS useQuery und useMutation in Komponenten
// components/UserList.tsx
import { trpc } from '../utils/trpc';
export function UserList() {
// Vollständige Typsicherheit: input UND output sind typisiert
const { data, isLoading, fetchNextPage } = trpc.user.list.useInfiniteQuery(
{ limit: 20 },
{ getNextPageParam: (page) => page.nextCursor }
);
const createUser = trpc.user.create.useMutation({
onSuccess(newUser) {
// Cache automatisch invalidieren nach Mutation
trpc.useUtils().user.list.invalidate();
console.log(`User erstellt: ${newUser.name}`);
},
onError(error) {
// Zod-Validierungsfehler direkt zugänglich
const zodErrors = error.data?.zodError?.fieldErrors;
console.error(zodErrors?.email?.[0] ?? error.message);
},
});
if (isLoading) return <div>Lade...</div>;
return (
<div>
{data?.pages.flatMap(p => p.users).map(user => (
// user.id, user.name, user.email — alle typsicher!
<UserCard key={user.id} user={user} />
))}
<button onClick={() => createUser.mutate({
name: 'Max Mustermann',
email: 'max@example.com',
})}>
User hinzufügen
</button>
</div>
);
}
6. Next.js App Router Integration
Mit dem Next.js App Router (Server Components) ändert sich die tRPC-Integration. Claude Code kennt beide Ansätze und wählt den richtigen für den jeweiligen Anwendungsfall.
APP ROUTER Server-seitiger tRPC-Aufruf (React Server Components)
// app/trpc/server.ts — Server-seitiger Client
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import { cache } from 'react';
import superjson from 'superjson';
import { type AppRouter } from '../../server/routers/_app';
// cache() stellt sicher: pro Request nur ein Client
const getServerClient = cache(() =>
createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({
url: `${process.env.NEXT_PUBLIC_URL}/api/trpc`,
transformer: superjson,
})],
})
);
// app/users/page.tsx — Server Component mit direktem tRPC-Aufruf
import { getServerClient } from '../trpc/server';
export default async function UsersPage() {
// Direkter Server-zu-Server-Aufruf — kein HTTP-Overhead!
const { users } = await getServerClient().user.list.query({ limit: 10 });
return (
<main>
<h1>Benutzer</h1>
{users.map(u => <p key={u.id}>{u.name}</p>)}
</main>
);
}
// Für Client-Komponenten im App Router: React Query Provider
// app/providers.tsx
'use client';
import { TRPCReactProvider } from '../utils/trpc';
export function Providers({ children }) {
return <TRPCReactProvider>{children}</TRPCReactProvider>;
}
App Router Achtung: Server Components können tRPC direkt aufrufen (kein useQuery). Client Components (mit 'use client') brauchen weiterhin den React Query Provider und trpc.*.useQuery()-Hooks.
7. Zod-Validierung: Fortgeschrittene Muster
Claude Code nutzt Zods gesamten Funktionsumfang für komplexe Input-Validierungen:
// Komplexe Zod-Schemas die Claude Code generiert
import { z } from 'zod';
// Diskriminierte Union für verschiedene Event-Typen
const eventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('USER_CREATED'), userId: z.string().uuid() }),
z.object({ type: z.literal('POST_PUBLISHED'), postId: z.string().uuid(), publishedAt: z.date() }),
]);
// Rekursives Schema für Kommentar-Threads
type Comment = {
id: string; text: string; replies: Comment[];
};
const commentSchema: z.ZodType<Comment> = z.lazy(() =>
z.object({
id: z.string(),
text: z.string().min(1).max(2000),
replies: z.array(commentSchema),
})
);
// Custom Refinement: Geschäftslogik in der Validierung
const dateRangeSchema = z.object({
from: z.date(),
to: z.date(),
}).refine(
(data) => data.from < data.to,
{ message: 'Startdatum muss vor Enddatum liegen', path: ['from'] }
);
8. tRPC vs REST vs GraphQL — Der Vergleich 2026
Wann ist tRPC die richtige Wahl? Claude Code hilft bei der Architekturentscheidung:
| Kriterium |
tRPC |
REST + OpenAPI |
GraphQL |
| Typsicherheit |
✓ Nativ, ohne Codegen |
~ Via Codegen (openapi-ts) |
~ Via Codegen (graphql-codegen) |
| Setup-Aufwand |
✓ Minimal (~30min) |
~ Mittel |
✗ Hoch (Schema, Resolver, Codegen) |
| Externe API-Clients |
✗ Nur TypeScript |
✓ Jede Sprache |
✓ Jede Sprache |
| Flexible Queries |
~ Vordefinierte Felder |
~ Query-Parameter |
✓ Client wählt Felder |
| Performance |
✓ HTTP Batching |
✓ HTTP/2 native |
~ N+1-Problem möglich |
| Ideal für |
Full-Stack TypeScript Monorepos |
Öffentliche APIs, Multi-Client |
Komplexe Datenmodelle, viele Clients |
Claude Code Empfehlung 2026: Für neue Next.js- oder Remix-Projekte mit TypeScript auf beiden Seiten ist tRPC die erste Wahl. Sobald externe Clients (Mobile Apps anderer Teams, Drittanbieter) die API nutzen sollen, ist REST mit OpenAPI oder GraphQL sinnvoller.
Claude Code als tRPC-Assistent: Konkrete Prompts
Claude Code versteht tRPC auf Architekturebene. Diese Prompts liefern direkt verwendbare Ergebnisse:
Effektive Claude Code Prompts für tRPC
- "Erstelle einen tRPC Router für Blog-Posts mit CRUD-Operationen, Zod-Validierung und Pagination — Prisma als ORM"
- "Füge eine Auth-Middleware hinzu, die NextAuth-Sessions prüft und den Context typsicher erweitert"
- "Migriere diese REST-API-Route zu einer tRPC Procedure mit identischem Verhalten"
- "Erstelle einen optimistischen Update-Pattern mit useMutation und Cache-Invalidierung für die User-Liste"
- "Zeige mir wie ich tRPC Procedures in Next.js App Router Server Actions einbinde"
Der entscheidende Vorteil: Claude Code sieht den gesamten Router-Typ und kann Client-Komponenten generieren, die perfekt zur Server-API passen — ohne einen manuellen Codegen-Schritt dazwischen.
API-Modul im Kurs
Im Claude Code Mastery Kurs: vollständiges tRPC-Modul mit Router-Design, Middleware, Authentifizierung, React Query Integration und Next.js App Router — für vollständige End-to-End-Typsicherheit.
14 Tage kostenlos testen →