tRPC lets you build fully type-safe APIs without writing a schema or code-generation step. Your TypeScript types flow from the server router directly to the client — so every API call is autocompleted, validated at compile time, and refactoring-safe. Use this skill when building TypeScript monorepos, Next.js apps, or any project where the server and client share a codebase.
When to Use This Skill
Use when building a TypeScript full-stack app (Next.js, Remix, Express + React) where the client and server share a single repo
Use when you want end-to-end type safety on API calls without REST/GraphQL schema overhead
Use when adding real-time features (subscriptions) to an existing tRPC setup
Use when designing multi-step middleware (auth, rate limiting, tenant scoping) on tRPC procedures
Use when migrating an existing REST/GraphQL API to tRPC incrementally
Core Concepts
Routers and Procedures
A router groups related procedures (think: endpoints). Procedures are typed functions — query for reads, mutation for writes, subscription for real-time streams.
Input Validation with Zod
All procedure inputs are validated with Zod schemas. The validated, typed input is available in the procedure handler — no manual parsing.
Context
context is shared state passed to every procedure — auth session, database client, request headers, etc. It is built once per request in a context factory. Important: Next.js App Router and Pages Router require separate context factories because App Router handlers receive a fetch Request, not a Node.js NextApiRequest.
Middleware
Middleware chains run before a procedure. Use them for authentication, logging, and request enrichment. They can extend the context for downstream procedures.
Next.js App Router handlers receive a fetch Request (not a Node.js NextApiRequest), so the context
must be built differently depending on the call site. Define one factory per surface:
// src/server/context.tsimport { typeFetchCreateContextFnOptions } from'@trpc/server/adapters/fetch';
import { auth } from'@/server/auth'; // Next-Auth v5 / your auth helperimport { db } from'./db';
/**
* Context for the HTTP handler (App Router Route Handler).
* `opts.req` is the fetch Request — auth is resolved server-side via `auth()`.
*/exportasyncfunctioncreateTRPCContext(opts: FetchCreateContextFnOptions) {
const session = awaitauth(); // server-side auth — no req/res neededreturn { session, db, headers: opts.req.headers };
}
/**
* Context for direct server-side callers (Server Components, RSC, cron jobs).
* No HTTP request is involved, so we call auth() directly from the server.
*/exportasyncfunctioncreateServerContext() {
const session = awaitauth();
return { session, db };
}
exporttypeContext = Awaited<ReturnType<typeof createTRPCContext>>;
Step 3: Build an Auth Middleware and Protected Procedure
// src/server/root.tsimport { router } from'./trpc';
import { postRouter } from'./routers/post';
import { userRouter } from'./routers/user';
exportconst appRouter = router({
post: postRouter,
user: userRouter,
});
// Export the type for the client — never import the appRouter itself on the clientexporttypeAppRouter = typeof appRouter;
Step 6: Mount the API Handler (Next.js App Router)
The App Router handler must use fetchRequestHandler and the fetch-based context factory.
createTRPCContext receives FetchCreateContextFnOptions (with a fetch Request), not
a Pages Router req/res pair.
// Client usage — requires wsLink in the client config
trpc.notification.onNew.useSubscription(undefined, {
onData(data) {
toast(data.message);
},
});
Best Practices
✅ Export only AppRouter type from server code — never import appRouter on the client
✅ Use separate context factories — createTRPCContext for the HTTP handler, createServerContext for Server Components and callers
✅ Validate all inputs with Zod — never trust raw input without a schema
✅ Split routers by domain (posts, users, billing) and merge in root.ts
✅ Extend context in middleware rather than querying the DB multiple times per request
✅ Use utils.invalidate() after mutations to keep the cache fresh
❌ Don't cast context with as any to silence type errors — the mismatch will surface as a runtime failure when auth or session lookups return undefined
❌ Don't use createContext({} as any) in Server Components — use createServerContext() which calls auth() directly
❌ Don't put business logic in the route handler — keep it in the procedure or a service layer
❌ Don't share the tRPC client instance globally — create it per-provider to avoid stale closures
Security & Safety Notes
Always enforce authorization in protectedProcedure — never rely on client-side checks alone
Validate all input shapes with Zod, including pagination cursors and IDs, to prevent injection via malformed inputs
Avoid exposing internal error details to clients — use TRPCError with a public-safe message and keep stack traces server-side only
Rate-limit public procedures using middleware to prevent abuse
Common Pitfalls
Problem: Auth session is null in protected procedures even when the user is logged in
Solution: Ensure createTRPCContext uses the correct server-side auth call (e.g. auth() from Next-Auth v5) and is not receiving a Pages Router req/res cast via as any in an App Router handler
Problem: Server Component caller fails for auth-dependent queries
Solution: Use createServerContext() (the dedicated server-side factory) instead of passing an empty or synthetic object to createContext
Problem: "Type error: AppRouter is not assignable to AnyRouter"
Solution: Import AppRouter as a type import (import type { AppRouter }) on the client, not the full module
Problem: Mutations not reflecting in the UI after success
Solution: Call utils.<router>.<procedure>.invalidate() in onSuccess to trigger a refetch via React Query
Problem: "Cannot find module '@trpc/server/adapters/next'" with App Router
Solution: Use @trpc/server/adapters/fetch and fetchRequestHandler for the App Router; the nextjs adapter is for Pages Router only
Problem: Subscriptions not connecting
Solution: Subscriptions require splitLink — route subscriptions to wsLink and queries/mutations to httpBatchLink
Related Skills
@typescript-expert — Deep TypeScript patterns used inside tRPC routers and generic utilities
@react-patterns — React hooks patterns that pair with trpc.*.useQuery and useMutation
@test-driven-development — Write procedure unit tests using createCallerFactory without an HTTP server
@security-auditor — Review tRPC middleware chains for auth bypass and input validation gaps