DEV Community

Olivia Craft
Olivia Craft

Posted on

CLAUDE.md for tRPC: 13 Rules That Keep AI Inside the Type-Safe RPC Model

tRPC is a type-safe RPC framework that lets TypeScript clients call server functions as if they were local — with end-to-end type inference and no code generation. AI tools that treat it like REST or GraphQL miss the entire point.

The most common failure modes: defining routers like REST controllers, breaking the type inference chain with any or manual type casts, adding unnecessary serialization, and not using the built-in React Query integration for client-side data fetching.

A CLAUDE.md file that declares the tRPC model prevents these patterns. Here are 13 rules.


Rule 1: Procedures, not endpoints

tRPC uses procedures (query, mutation, subscription), not HTTP endpoints.
Do NOT name procedures like REST resources (getUserById, createUser).
Do NOT add route prefixes or HTTP method annotations.
Procedures are typed functions — name them as verbs or descriptors:
  user.getById, user.create, post.list, auth.login
Enter fullscreen mode Exit fullscreen mode

Rule 2: End-to-end type inference must be preserved

The type inference chain from server to client must never be broken.
Do NOT use 'any', 'unknown' without narrowing, or explicit type casts
in router definitions, middleware, or context.
The AppRouter type export from the server is the single source of truth.
Client types derive automatically — do not duplicate them.
Enter fullscreen mode Exit fullscreen mode

Rule 3: Input validation uses Zod

All procedure inputs are validated with Zod schemas:
  .input(z.object({ id: z.string(), name: z.string().min(1) }))
Do NOT validate inputs manually in the procedure handler.
Zod schemas serve as both runtime validation and TypeScript type source.
Enter fullscreen mode Exit fullscreen mode

Rule 4: Context carries authentication and shared dependencies

Authentication, database connections, and request context belong in
the createContext function, not in individual procedures.
Middleware (authedProcedure, adminProcedure) extends the base procedure
to add typed context fields.
Do NOT access req/res directly inside procedure handlers.
Enter fullscreen mode Exit fullscreen mode

Rule 5: The React client uses tRPC hooks, not fetch

Client-side data fetching uses tRPC's React Query integration:
  trpc.user.getById.useQuery({ id })
  trpc.user.create.useMutation()
Do NOT use fetch(), axios, or raw useEffect for tRPC calls.
The hook names mirror the router structure exactly.
Enter fullscreen mode Exit fullscreen mode

Rule 6: Router structure is the API contract

The router tree defines the namespace:
  appRouter = router({
    user: userRouter,
    post: postRouter,
    auth: authRouter,
  })
Do NOT flatten all procedures into a single root router.
Sub-routers group related procedures  mirror the domain model.
Enter fullscreen mode Exit fullscreen mode

Rule 7: Error handling uses TRPCError

Throw TRPCError for expected errors:
  throw new TRPCError({ code: 'NOT_FOUND', message: '...' })
Error codes map to HTTP status codes automatically.
Do NOT throw plain Error objects or return { success: false } objects.
Use the code field: NOT_FOUND, UNAUTHORIZED, BAD_REQUEST, INTERNAL_SERVER_ERROR
Enter fullscreen mode Exit fullscreen mode

Rule 8: Subscriptions use observable or async generators

Real-time procedures use .subscription() with observable or async generator:
  .subscription(() => observable((emit) => { ... }))
Do NOT implement polling as a workaround for subscriptions.
WebSocket transport is required for subscriptions.
Enter fullscreen mode Exit fullscreen mode

Rule 9: Middleware is reusable typed context extension

Shared behavior (auth checks, rate limiting, logging) uses middleware:
  const authedProcedure = publicProcedure.use(({ ctx, next }) => {
    if (!ctx.session) throw new TRPCError({ code: 'UNAUTHORIZED' })
    return next({ ctx: { ...ctx, user: ctx.session.user } })
  })
Do NOT copy auth checks into individual procedure handlers.
Enter fullscreen mode Exit fullscreen mode

Rule 10: Output validation is optional but explicit

Output types are inferred automatically from the return type.
Use .output(schema) only when explicit runtime validation is required.
Do NOT add .output() to every procedure  it doubles the validation overhead.
Add .output() when the return data comes from an untrusted source.
Enter fullscreen mode Exit fullscreen mode

Rule 11: Links configure the client transport

The tRPC client is configured with links:
  httpBatchLink for standard HTTP (batches multiple calls)
  wsLink for WebSocket subscriptions
  splitLink for mixed HTTP + WebSocket
Do NOT configure fetch directly  use the link layer.
Enter fullscreen mode Exit fullscreen mode

Rule 12: Server-side calls use createCaller

To call tRPC procedures from server-side code (SSR, cron jobs, tests):
  const caller = appRouter.createCaller(context)
  const result = await caller.user.getById({ id })
Do NOT expose internal procedure handlers as standalone functions.
createCaller preserves the middleware and context chain.
Enter fullscreen mode Exit fullscreen mode

Rule 13: File structure mirrors the router tree

Router files should mirror the namespace:
  server/routers/user.ts → userRouter
  server/routers/post.ts → postRouter
  server/trpc.ts → base procedure, context, middleware
  server/root.ts → appRouter assembly
Do NOT define all procedures in a single file.
Enter fullscreen mode Exit fullscreen mode

The full CLAUDE.md block

# tRPC Project Rules

## Core Model
- Procedures (query/mutation/subscription), not HTTP endpoints
- End-to-end type inference must be preserved — no 'any' in router code
- AppRouter type is the single source of truth for client types

## Input Validation
- All inputs validated with Zod schemas via .input()
- No manual validation in procedure handlers

## Client
- React Query integration: trpc.[namespace].[procedure].useQuery/useMutation
- No fetch(), axios, or raw useEffect for tRPC calls

## Context & Auth
- Auth and shared deps in createContext
- Middleware (authedProcedure, adminProcedure) for access control
- No direct req/res access in procedure handlers

## Errors
- Throw TRPCError with code field (NOT_FOUND, UNAUTHORIZED, etc.)
- No plain Error objects or { success: false } return patterns

## Router Structure
- Nested sub-routers mirror the domain model
- File structure mirrors the router tree
- Server calls use createCaller (not standalone functions)

## Transport
- HTTP: httpBatchLink
- WebSocket: wsLink + splitLink
- No manual fetch config
Enter fullscreen mode Exit fullscreen mode

Why tRPC AI drift is predictable

REST is the default mental model for APIs. GraphQL is the default for "typed APIs." tRPC is newer (v10 stable in 2022, v11 in 2024) and has far less training data than either.

Without explicit instruction, AI writes tRPC as REST-with-types: procedures named like endpoints, manual type exports, fetch calls on the client instead of hooks.

The CLAUDE.md declares the tRPC model before any router is defined.


These 13 rules are part of a framework-specific CLAUDE.md series. The Cursor Rules Pack (50 production-tested rules across all stacks) is available at oliviacraftlat.gumroad.com/l/wyaeil — search "Olivia Craft Cursor Rules" on Gumroad.

Top comments (0)