[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6KS37MSBYYJTT2FFM6K2WAC":3,"skill-same:01M6KS37MSBYYJTT2FFM6K2WAC":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":53,"repositoryHtmlUrl":54,"repositoryHomepageUrl":6,"ownerHtmlUrl":55,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":56,"install":57,"resources":69},"01M6KS37MSBYYJTT2FFM6K2WAC","trpc-type-safety",null,"Builds end-to-end type-safe tRPC routers with Zod input validation.","JackSmack1971\u002Ffullstack-council","JackSmack1971","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F128181597?v=4","01M6KS1FH32N6WNWZ68JC9FFDJ","01M6KS1FNAEAKMQ2K1084NTEKF",1,"MIT","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fclaude-mpm-skills\u002Ftrpc-type-safety\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"trpc type safety","Type Safety","Safety","trpc","type","npx","with","Input Validation","type-safety","add","input","end","safe","files","list","read","zod","file","https","github","2026-11-10 10:12:30",false,"---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\nsource:     https:\u002F\u002Fskills.sh\u002Fbobmatnyc\u002Fclaude-mpm-skills\u002Ftrpc-type-safety\ngithub:     https:\u002F\u002Fgithub.com\u002Fbobmatnyc\u002Fclaude-mpm-skills\ninstall:    npx skills add bobmatnyc\u002Fclaude-mpm-skills --skill trpc-type-safety\ninstalls:   \nscraped_at: 2026-04-04T15:07:58Z\nallowed-tools: [read_file, list_files]\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Summary\n\n- Shares TypeScript types between client and server automatically—no codegen required, with full type inference across queries, mutations, and subscriptions\n- Integrates seamlessly with React Query for caching, optimistic updates, and infinite queries; supports Next.js App Router, Server Components, and WebSocket subscriptions\n- Validates all inputs with Zod schemas; includes middleware for authentication, logging, and rate limiting; error handling with typed TRPCError codes\n- Supports batching, DataLoader patterns, file uploads via signed URLs, and production patterns like caching, monitoring, and OpenTelemetry tracing\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n- tRPC: Full-stack TypeScript, monorepo, internal tools\n- REST: Public APIs, language-agnostic, broad compatibility\n- GraphQL: Complex data graphs, multiple clients, flexible queries\n- Router: Collection of procedures (API endpoints)\n- Procedure: Single API operation (query or mutation)\n- Context: Request-scoped data (user, database, etc.)\n- Middleware: Intercept\u002Fmodify requests (auth, logging)\n- Input\u002FOutput: Validated with Zod schemas\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n- Add tRPC alongside REST: Don't rewrite everything at once\n- New features in tRPC: Start with new endpoints\n- Migrate high-value endpoints: Focus on complex or frequently used APIs\n- Keep public APIs in REST: Only migrate internal consumption\n- Use batching for multiple queries:httpBatchLink({url:'\u002Fapi\u002Ftrpc',maxBatchSize:10})\n- Implement DataLoader for N+1 queries:constuserLoader=newDataLoader(batchLoadUsers);\n- Cache expensive queries:trpc.posts.list.useQuery(undefined,{staleTime:5*60*1000});\n- Optimize database queries:\u002F\u002F ❌ Bad: N+1 queryconstposts=awaitdb.post.findMany();constpostsWithAuthors=awaitPromise.all(posts.map((p)=>db.user.findUnique({where:{id:p.authorId}})));\u002F\u002F ✅ Good: Single query with includeconstposts=awaitdb.post.findMany({include:{author:true},});\n- Use React Query's deduplication:\u002F\u002F Multiple components can call same query—React Query deduplicatesconst{data}=trpc.user.getMe.useQuery();\n- Always validate input with Zod\n- Use middleware for authentication:constprotectedProcedure=t.procedure.use(isAuthed);\n- Sanitize error messages in production\n- Implement rate limiting\n- Use HTTPS in production\n- Set CORS properly:createNextApiHandler({router:appRouter,createContext,onError:({error})=>{if(error.code==='INTERNAL_SERVER_ERROR'){console.error('Internal error:',error);}},});\n- Export router type, not implementation:exporttypeAppRouter=typeofappRouter;\u002F\u002F ✅\u002F\u002F Don't export `appRouter` itself to client\n- Usesatisfiesfor better inference:constinput={name:'Alice',age:30,}satisfies CreateUserInput;\n- Avoidanyin context:\u002F\u002F ❌ Badctx:{user:any}\u002F\u002F ✅ Goodctx:{user:User|null}\n- Define schema first: Write Zod schemas before procedures\n- Test procedures in isolation: UsecreateCallerfor unit tests\n- Use TypeScript strict mode: Catch type errors early\n- Enable React Query DevTools:import{ReactQueryDevtools}from'@tanstack\u002Freact-query-devtools';\u003CReactQueryDevtools initialIsOpen={false}\u002F>\n- ✅No code generation: Types inferred from TypeScript\n- ✅React Query integration: Built-in caching and optimistic updates\n- ✅Next.js first-class support: App Router, Server Components\n- ✅Developer experience: Auto-complete, refactoring, type errors\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n- tRPC: Full-stack TypeScript, monorepo, internal tools\n- REST: Public APIs, language-agnostic, broad compatibility\n- GraphQL: Complex data graphs, multiple clients, flexible queries\n- Router: Collection of procedures (API endpoints)\n- Procedure: Single API operation (query or mutation)\n- Context: Request-scoped data (user, database, etc.)\n- Middleware: Intercept\u002Fmodify requests (auth, logging)\n- Input\u002FOutput: Validated with Zod schemas\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n- Add tRPC alongside REST: Don't rewrite everything at once\n- New features in tRPC: Start with new endpoints\n- Migrate high-value endpoints: Focus on complex or frequently used APIs\n- Keep public APIs in REST: Only migrate internal consumption\n- Use batching for multiple queries:httpBatchLink({url:'\u002Fapi\u002Ftrpc',maxBatchSize:10})\n- Implement DataLoader for N+1 queries:constuserLoader=newDataLoader(batchLoadUsers);\n- Cache expensive queries:trpc.posts.list.useQuery(undefined,{staleTime:5*60*1000});\n- Optimize database queries:\u002F\u002F ❌ Bad: N+1 queryconstposts=awaitdb.post.findMany();constpostsWithAuthors=awaitPromise.all(posts.map((p)=>db.user.findUnique({where:{id:p.authorId}})));\u002F\u002F ✅ Good: Single query with includeconstposts=awaitdb.post.findMany({include:{author:true},});\n- Use React Query's deduplication:\u002F\u002F Multiple components can call same query—React Query deduplicatesconst{data}=trpc.user.getMe.useQuery();\n- Always validate input with Zod\n- Use middleware for authentication:constprotectedProcedure=t.procedure.use(isAuthed);\n- Sanitize error messages in production\n- Implement rate limiting\n- Use HTTPS in production\n- Set CORS properly:createNextApiHandler({router:appRouter,createContext,onError:({error})=>{if(error.code==='INTERNAL_SERVER_ERROR'){console.error('Internal error:',error);}},});\n- Export router type, not implementation:exporttypeAppRouter=typeofappRouter;\u002F\u002F ✅\u002F\u002F Don't export `appRouter` itself to client\n- Usesatisfiesfor better inference:constinput={name:'Alice',age:30,}satisfies CreateUserInput;\n- Avoidanyin context:\u002F\u002F ❌ Badctx:{user:any}\u002F\u002F ✅ Goodctx:{user:User|null}\n- Define schema first: Write Zod schemas before procedures\n- Test procedures in isolation: UsecreateCallerfor unit tests\n- Use TypeScript strict mode: Catch type errors early\n- Enable React Query DevTools:import{ReactQueryDevtools}from'@tanstack\u002Freact-query-devtools';\u003CReactQueryDevtools initialIsOpen={false}\u002F>\n- ✅No code generation: Types inferred from TypeScript\n- ✅React Query integration: Built-in caching and optimistic updates\n- ✅Next.js first-class support: App Router, Server Components\n- ✅Developer experience: Auto-complete, refactoring, type errors\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## SKILL.md\n\n# tRPC - End-to-End Type Safety\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## progressive\\_disclosure: entry\\_point: summary sections: - id: summary title: \"tRPC Overview\" tokens: 70 next: [when\\_to\\_use, quick\\_start] - id: when\\_to\\_use title: \"When to Use tRPC\" tokens: 150 next: [quick\\_start, core\\_concepts] - id: quick\\_start title: \"Quick Start\" tokens: 300 next: [core\\_concepts, router\\_definition] - id: core\\_concepts title: \"Core Concepts\" tokens: 400 next: [router\\_definition, procedures] - id: router\\_definition title: \"Router Definition\" tokens: 350 next: [procedures, context] - id: procedures title: \"Procedures (Query & Mutation)\" tokens: 400 next: [input\\_validation, context] - id: input\\_validation title: \"Input Validation with Zod\" tokens: 350 next: [context, middleware] - id: context title: \"Context Management\" tokens: 400 next: [middleware, error\\_handling] - id: middleware title: \"Middleware\" tokens: 400 next: [error\\_handling, client\\_setup] - id: error\\_handling title: \"Error Handling\" tokens: 350 next: [client\\_setup, react\\_integration] - id: client\\_setup title: \"Client Setup\" tokens: 400 next: [react\\_integration, nextjs\\_integration] - id: react\\_integration title: \"React Query Integration\" tokens: 450 next: [nextjs\\_integration, subscriptions] - id: nextjs\\_integration title: \"Next.js App Router Integration\" tokens: 500 next: [subscriptions, file\\_uploads] - id: subscriptions title: \"Real-time Subscriptions\" tokens: 400 next: [file\\_uploads, batching] - id: file\\_uploads title: \"File Uploads\" tokens: 300 next: [batching, typescript\\_inference] - id: batching title: \"Batch Requests & Data Loaders\" tokens: 350 next: [typescript\\_inference, testing] - id: typescript\\_inference title: \"TypeScript Inference Patterns\" tokens: 300 next: [testing, production\\_patterns] - id: testing title: \"Testing Strategies\" tokens: 400 next: [production\\_patterns, comparison] - id: production\\_patterns title: \"Production Patterns\" tokens: 450 next: [comparison, migration] - id: comparison title: \"Comparison with REST & GraphQL\" tokens: 250 next: [migration, best\\_practices] - id: migration title: \"Migration from REST\" tokens: 300 next: [best\\_practices] - id: best\\_practices title: \"Best Practices & Performance\" tokens: 400\n\n## Summary\n\n**tRPC** enables end-to-end type safety between TypeScript clients and servers without code generation. Define your API once, get automatic type inference everywhere.\n\n**Key Benefits**: Zero codegen, TypeScript inference, React Query integration, minimal boilerplate.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## When to Use tRPC\n\n**✅ Perfect For**:\n\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n\n**❌ Avoid When**:\n\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n\n**When to Choose**:\n\n- **tRPC**: Full-stack TypeScript, monorepo, internal tools\n- **REST**: Public APIs, language-agnostic, broad compatibility\n- **GraphQL**: Complex data graphs, multiple clients, flexible queries\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Quick Start\n\n### Installation\n\n```\n# Server dependencies\nnpm install @trpc\u002Fserver zod\n\n# React\u002FNext.js client dependencies\nnpm install @trpc\u002Fclient @trpc\u002Freact-query @tanstack\u002Freact-query\n```\n\n### Define Router (Server)\n\n```\n\u002F\u002F server\u002Ftrpc.ts\nimport { initTRPC } from '@trpc\u002Fserver';\nimport { z } from 'zod';\n\nconst t = initTRPC.create();\n\nexport const appRouter = t.router({\n  hello: t.procedure\n    .input(z.object({ name: z.string() }))\n    .query(({ input }) => {\n      return { greeting: `Hello ${input.name}` };\n    }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string(), content: z.string() }))\n    .mutation(async ({ input }) => {\n      \u002F\u002F Save to database\n      return { id: 1, ...input };\n    }),\n});\n\nexport type AppRouter = typeof appRouter;\n```\n\n### Use in Client (React)\n\n```\n\u002F\u002F client\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '..\u002Fserver\u002Ftrpc';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n\n\u002F\u002F Component\nfunction MyComponent() {\n  const { data } = trpc.hello.useQuery({ name: 'World' });\n  const createPost = trpc.createPost.useMutation();\n\n  return \u003Cdiv>{data?.greeting}\u003C\u002Fdiv>; \u002F\u002F Fully typed!\n}\n```\n\n**Next**: Learn core concepts or dive into router definition.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Core Concepts\n\n### The tRPC Philosophy\n\ntRPC provides **type-safe remote procedure calls** by sharing TypeScript types between client and server. No code generation—just TypeScript's inference.\n\n### Key Components\n\n1. **Router**: Collection of procedures (API endpoints)\n2. **Procedure**: Single API operation (query or mutation)\n3. **Context**: Request-scoped data (user, database, etc.)\n4. **Middleware**: Intercept\u002Fmodify requests (auth, logging)\n5. **Input\u002FOutput**: Validated with Zod schemas\n\n### Type Flow\n\n```\n\u002F\u002F Server defines types\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ input }) => ({ id: input, name: 'Alice' })),\n});\n\n\u002F\u002F Client gets automatic types\nconst user = await trpc.getUser.query('123');\n\u002F\u002F user is typed as { id: string, name: string }\n```\n\n### Architecture Pattern\n\n```\n┌─────────────┐     Type-safe     ┌──────────────┐\n│   Client    │ ←────────────────→ │    Server    │\n│ (React)     │   No codegen!      │   (Node.js)  │\n└─────────────┘                    └──────────────┘\n      ↓                                    ↓\n React Query                          tRPC Router\n (caching)                            (procedures)\n```\n\n**Advantages**:\n\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Router Definition\n\n### Basic Router Structure\n\n```\nimport { initTRPC } from '@trpc\u002Fserver';\n\nconst t = initTRPC.create();\n\nexport const appRouter = t.router({\n  \u002F\u002F Procedures go here\n});\n\nexport type AppRouter = typeof appRouter;\n```\n\n### Nested Routers (Namespacing)\n\n```\nconst userRouter = t.router({\n  getById: t.procedure\n    .input(z.string())\n    .query(({ input }) => getUser(input)),\n\n  create: t.procedure\n    .input(z.object({ name: z.string(), email: z.string() }))\n    .mutation(({ input }) => createUser(input)),\n});\n\nconst postRouter = t.router({\n  list: t.procedure.query(() => getPosts()),\n  create: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(({ input }) => createPost(input)),\n});\n\nexport const appRouter = t.router({\n  user: userRouter,\n  post: postRouter,\n});\n\n\u002F\u002F Client usage:\n\u002F\u002F trpc.user.getById.useQuery('123')\n\u002F\u002F trpc.post.list.useQuery()\n```\n\n### Router Merging\n\n```\nimport { adminRouter } from '.\u002Fadmin';\nimport { publicRouter } from '.\u002Fpublic';\n\nexport const appRouter = t.mergeRouters(publicRouter, adminRouter);\n```\n\n### Router Organization Best Practices\n\n```\nserver\u002F\n├── trpc.ts           # tRPC instance, context, middleware\n├── routers\u002F\n│   ├── user.ts       # User-related procedures\n│   ├── post.ts       # Post-related procedures\n│   └── index.ts      # Combine all routers\n└── index.ts          # Export AppRouter type\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Procedures (Query & Mutation)\n\n### Query Procedures (Read Operations)\n\n```\nconst router = t.router({\n  \u002F\u002F Simple query\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ input }) => {\n      return db.user.findUnique({ where: { id: input } });\n    }),\n\n  \u002F\u002F Query with multiple inputs\n  searchUsers: t.procedure\n    .input(z.object({\n      query: z.string(),\n      limit: z.number().default(10),\n    }))\n    .query(({ input }) => {\n      return db.user.findMany({\n        where: { name: { contains: input.query } },\n        take: input.limit,\n      });\n    }),\n});\n```\n\n### Mutation Procedures (Write Operations)\n\n```\nconst router = t.router({\n  createUser: t.procedure\n    .input(z.object({\n      name: z.string().min(3),\n      email: z.string().email(),\n    }))\n    .mutation(async ({ input }) => {\n      return await db.user.create({ data: input });\n    }),\n\n  updateUser: t.procedure\n    .input(z.object({\n      id: z.string(),\n      data: z.object({\n        name: z.string().optional(),\n        email: z.string().email().optional(),\n      }),\n    }))\n    .mutation(async ({ input }) => {\n      return await db.user.update({\n        where: { id: input.id },\n        data: input.data,\n      });\n    }),\n});\n```\n\n### Query vs Mutation\n\n| Aspect | Query | Mutation |\n| --- | --- | --- |\n| **Purpose** | Read data | Modify data |\n| **HTTP Method** | GET | POST |\n| **Caching** | Cached by React Query | Not cached |\n| **Idempotent** | Yes | No |\n| **Side Effects** | None | Database writes, emails, etc. |\n\n### Output Typing\n\n```\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .output(z.object({ id: z.string(), name: z.string() })) \u002F\u002F Optional\n    .query(({ input }) => {\n      return { id: input, name: 'Alice' };\n    }),\n});\n```\n\n**Note**: Output validation adds runtime overhead—use for critical data only.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Input Validation with Zod\n\n### Why Zod?\n\ntRPC uses **Zod** for runtime type validation and TypeScript inference. Zod schemas provide:\n\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n\n### Basic Validation\n\n```\nimport { z } from 'zod';\n\nconst router = t.router({\n  createPost: t.procedure\n    .input(z.object({\n      title: z.string().min(5).max(100),\n      content: z.string(),\n      published: z.boolean().default(false),\n      tags: z.array(z.string()).optional(),\n    }))\n    .mutation(({ input }) => {\n      \u002F\u002F input is fully typed and validated\n      return createPost(input);\n    }),\n});\n```\n\n### Advanced Validation\n\n```\nconst createUserInput = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n  age: z.number().int().min(18),\n  role: z.enum(['user', 'admin']),\n  metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst router = t.router({\n  createUser: t.procedure\n    .input(createUserInput)\n    .mutation(({ input }) => {\n      \u002F\u002F All validation passed\n      return saveUser(input);\n    }),\n});\n```\n\n### Transformations\n\n```\nconst router = t.router({\n  getUser: t.procedure\n    .input(\n      z.object({\n        id: z.string().transform((id) => parseInt(id, 10)),\n      })\n    )\n    .query(({ input }) => {\n      \u002F\u002F input.id is now a number\n      return db.user.findUnique({ where: { id: input.id } });\n    }),\n});\n```\n\n### Reusable Schemas\n\n```\n\u002F\u002F schemas\u002Fuser.ts\nexport const CreateUserSchema = z.object({\n  name: z.string(),\n  email: z.string().email(),\n});\n\nexport const UpdateUserSchema = CreateUserSchema.partial().extend({\n  id: z.string(),\n});\n\n\u002F\u002F routers\u002Fuser.ts\nconst router = t.router({\n  create: t.procedure.input(CreateUserSchema).mutation(\u002F*...*\u002F),\n  update: t.procedure.input(UpdateUserSchema).mutation(\u002F*...*\u002F),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Context Management\n\n### What is Context?\n\n**Context** provides request-scoped data to all procedures—authentication, database connections, logging, etc.\n\n### Creating Context\n\n```\nimport { inferAsyncReturnType } from '@trpc\u002Fserver';\nimport { CreateNextContextOptions } from '@trpc\u002Fserver\u002Fadapters\u002Fnext';\n\nexport async function createContext(opts: CreateNextContextOptions) {\n  const session = await getSession(opts.req);\n\n  return {\n    session,\n    db: prisma,\n    req: opts.req,\n    res: opts.res,\n  };\n}\n\nexport type Context = inferAsyncReturnType\u003Ctypeof createContext>;\n\nconst t = initTRPC.context\u003CContext>().create();\n```\n\n### Using Context in Procedures\n\n```\nconst router = t.router({\n  getMe: t.procedure.query(({ ctx }) => {\n    if (!ctx.session?.user) {\n      throw new TRPCError({ code: 'UNAUTHORIZED' });\n    }\n\n    return ctx.db.user.findUnique({\n      where: { id: ctx.session.user.id },\n    });\n  }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(async ({ ctx, input }) => {\n      return ctx.db.post.create({\n        data: {\n          title: input.title,\n          authorId: ctx.session.user.id,\n        },\n      });\n    }),\n});\n```\n\n### Context Best Practices\n\n```\n\u002F\u002F ✅ Good: Lazy database connection\nexport async function createContext(opts: CreateNextContextOptions) {\n  return {\n    getDB: () => prisma, \u002F\u002F Lazy\n    session: await getSession(opts.req),\n  };\n}\n\n\u002F\u002F ❌ Bad: Heavy computation in context\nexport async function createContext(opts: CreateNextContextOptions) {\n  const allUsers = await prisma.user.findMany(); \u002F\u002F Too expensive!\n  return { allUsers };\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Middleware\n\n### What is Middleware?\n\nMiddleware intercepts procedure calls to add cross-cutting concerns: logging, timing, authentication, rate limiting.\n\n### Basic Middleware\n\n```\nconst loggerMiddleware = t.middleware(async ({ path, type, next }) => {\n  const start = Date.now();\n  console.log(`→ ${type} ${path}`);\n\n  const result = await next();\n\n  const duration = Date.now() - start;\n  console.log(`✓ ${type} ${path} - ${duration}ms`);\n\n  return result;\n});\n\nconst loggedProcedure = t.procedure.use(loggerMiddleware);\n```\n\n### Authentication Middleware\n\n```\nconst isAuthed = t.middleware(({ ctx, next }) => {\n  if (!ctx.session?.user) {\n    throw new TRPCError({ code: 'UNAUTHORIZED' });\n  }\n\n  return next({\n    ctx: {\n      ...ctx,\n      user: ctx.session.user, \u002F\u002F Narrow type\n    },\n  });\n});\n\n\u002F\u002F Protected procedure builder\nconst protectedProcedure = t.procedure.use(isAuthed);\n\nconst router = t.router({\n  \u002F\u002F Public\n  getPublicPosts: t.procedure.query(() => getPosts()),\n\n  \u002F\u002F Protected - requires authentication\n  getMyPosts: protectedProcedure.query(({ ctx }) => {\n    \u002F\u002F ctx.user is guaranteed to exist\n    return getPostsByUser(ctx.user.id);\n  }),\n});\n```\n\n### Chaining Middleware\n\n```\nconst timingMiddleware = t.middleware(async ({ next }) => {\n  const start = performance.now();\n  const result = await next();\n  console.log(`Execution time: ${performance.now() - start}ms`);\n  return result;\n});\n\nconst rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {\n  await checkRateLimit(ctx.session?.user?.id);\n  return next();\n});\n\nconst protectedProcedure = t.procedure\n  .use(timingMiddleware)\n  .use(rateLimitMiddleware)\n  .use(isAuthed);\n```\n\n### Context Transformation\n\n```\nconst enrichContextMiddleware = t.middleware(async ({ ctx, next }) => {\n  const user = ctx.session?.user\n    ? await ctx.db.user.findUnique({ where: { id: ctx.session.user.id } })\n    : null;\n\n  return next({\n    ctx: {\n      ...ctx,\n      user, \u002F\u002F Full user object\n    },\n  });\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Error Handling\n\n### TRPCError\n\n```\nimport { TRPCError } from '@trpc\u002Fserver';\n\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(async ({ input }) => {\n      const user = await db.user.findUnique({ where: { id: input } });\n\n      if (!user) {\n        throw new TRPCError({\n          code: 'NOT_FOUND',\n          message: `User ${input} not found`,\n        });\n      }\n\n      return user;\n    }),\n});\n```\n\n### Error Codes\n\n| Code | HTTP Status | Use Case |\n| --- | --- | --- |\n| `BAD_REQUEST` | 400 | Invalid input |\n| `UNAUTHORIZED` | 401 | Not authenticated |\n| `FORBIDDEN` | 403 | Not authorized |\n| `NOT_FOUND` | 404 | Resource not found |\n| `TIMEOUT` | 408 | Request timeout |\n| `CONFLICT` | 409 | Resource conflict |\n| `PRECONDITION_FAILED` | 412 | Precondition failed |\n| `PAYLOAD_TOO_LARGE` | 413 | Request too large |\n| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded |\n| `CLIENT_CLOSED_REQUEST` | 499 | Client closed connection |\n| `INTERNAL_SERVER_ERROR` | 500 | Server error |\n\n### Custom Error Handling\n\n```\nconst router = t.router({\n  deleteUser: t.procedure\n    .input(z.string())\n    .mutation(async ({ input, ctx }) => {\n      try {\n        return await ctx.db.user.delete({ where: { id: input } });\n      } catch (error) {\n        if (error.code === 'P2025') { \u002F\u002F Prisma not found\n          throw new TRPCError({\n            code: 'NOT_FOUND',\n            message: 'User not found',\n            cause: error,\n          });\n        }\n        throw new TRPCError({\n          code: 'INTERNAL_SERVER_ERROR',\n          message: 'Failed to delete user',\n          cause: error,\n        });\n      }\n    }),\n});\n```\n\n### Error Formatting\n\n```\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        zodError:\n          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError\n            ? error.cause.flatten()\n            : null,\n      },\n    };\n  },\n});\n```\n\n### Client-Side Error Handling\n\n```\nfunction MyComponent() {\n  const mutation = trpc.createUser.useMutation({\n    onError: (error) => {\n      if (error.data?.code === 'UNAUTHORIZED') {\n        router.push('\u002Flogin');\n      } else {\n        toast.error(error.message);\n      }\n    },\n  });\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Client Setup\n\n### Vanilla Client\n\n```\nimport { createTRPCProxyClient, httpBatchLink } from '@trpc\u002Fclient';\nimport type { AppRouter } from '.\u002Fserver';\n\nconst client = createTRPCProxyClient\u003CAppRouter>({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n    }),\n  ],\n});\n\n\u002F\u002F Usage\nconst user = await client.user.getById.query('123');\nconst newPost = await client.post.create.mutate({ title: 'Hello' });\n```\n\n### React Client Setup\n\n```\n\u002F\u002F utils\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '..\u002Fserver\u002Frouters';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n\n\u002F\u002F _app.tsx\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '..\u002Futils\u002Ftrpc';\n\nexport default function App({ Component, pageProps }: AppProps) {\n  const [queryClient] = useState(() => new QueryClient());\n  const [trpcClient] = useState(() =>\n    trpc.createClient({\n      links: [\n        httpBatchLink({\n          url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        \u003CComponent {...pageProps} \u002F>\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n### Next.js API Route\n\n```\n\u002F\u002F pages\u002Fapi\u002Ftrpc\u002F[trpc].ts\nimport { createNextApiHandler } from '@trpc\u002Fserver\u002Fadapters\u002Fnext';\nimport { appRouter } from '..\u002F..\u002F..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002F..\u002F..\u002Fserver\u002Fcontext';\n\nexport default createNextApiHandler({\n  router: appRouter,\n  createContext,\n});\n```\n\n### Headers & Authentication\n\n```\nconst trpcClient = trpc.createClient({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      headers: async () => {\n        const token = await getAuthToken();\n        return {\n          authorization: token ? `Bearer ${token}` : undefined,\n        };\n      },\n    }),\n  ],\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## React Query Integration\n\n### useQuery Hook\n\n```\nfunction UserProfile({ userId }: { userId: string }) {\n  const { data, isLoading, error } = trpc.user.getById.useQuery(userId);\n\n  if (isLoading) return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  if (error) return \u003Cdiv>Error: {error.message}\u003C\u002Fdiv>;\n\n  return \u003Cdiv>{data.name}\u003C\u002Fdiv>;\n}\n```\n\n### Query Options\n\n```\nconst { data } = trpc.posts.list.useQuery(undefined, {\n  refetchOnWindowFocus: false,\n  staleTime: 5 * 60 * 1000, \u002F\u002F 5 minutes\n  cacheTime: 10 * 60 * 1000, \u002F\u002F 10 minutes\n  retry: 3,\n  onSuccess: (data) => console.log('Fetched', data.length, 'posts'),\n});\n```\n\n### useMutation Hook\n\n```\nfunction CreatePostForm() {\n  const utils = trpc.useContext();\n\n  const createPost = trpc.post.create.useMutation({\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch\n      utils.post.list.invalidate();\n    },\n  });\n\n  const handleSubmit = (data: { title: string }) => {\n    createPost.mutate(data);\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" \u002F>\n      \u003Cbutton disabled={createPost.isLoading}>\n        {createPost.isLoading ? 'Creating...' : 'Create'}\n      \u003C\u002Fbutton>\n      {createPost.error && \u003Cp>{createPost.error.message}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Optimistic Updates\n\n```\nconst createPost = trpc.post.create.useMutation({\n  onMutate: async (newPost) => {\n    \u002F\u002F Cancel outgoing refetches\n    await utils.post.list.cancel();\n\n    \u002F\u002F Snapshot previous value\n    const previousPosts = utils.post.list.getData();\n\n    \u002F\u002F Optimistically update\n    utils.post.list.setData(undefined, (old) => [\n      ...(old ?? []),\n      { id: 'temp', ...newPost },\n    ]);\n\n    return { previousPosts };\n  },\n  onError: (err, newPost, context) => {\n    \u002F\u002F Rollback on error\n    utils.post.list.setData(undefined, context?.previousPosts);\n  },\n  onSettled: () => {\n    \u002F\u002F Refetch after success or error\n    utils.post.list.invalidate();\n  },\n});\n```\n\n### Infinite Queries\n\n```\n\u002F\u002F Server\nconst router = t.router({\n  posts: t.procedure\n    .input(z.object({\n      cursor: z.number().optional(),\n      limit: z.number().default(10),\n    }))\n    .query(({ input }) => {\n      const posts = getPosts(input.cursor, input.limit);\n      return {\n        posts,\n        nextCursor: posts.length === input.limit ? input.cursor + input.limit : undefined,\n      };\n    }),\n});\n\n\u002F\u002F Client\nfunction PostList() {\n  const {\n    data,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = trpc.posts.useInfiniteQuery(\n    { limit: 10 },\n    {\n      getNextPageParam: (lastPage) => lastPage.nextCursor,\n    }\n  );\n\n  return (\n    \u003Cdiv>\n      {data?.pages.map((page) =>\n        page.posts.map((post) => \u003CPostCard key={post.id} post={post} \u002F>)\n      )}\n      {hasNextPage && (\n        \u003Cbutton onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>\n          Load More\n        \u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Next.js App Router Integration\n\n### Server Components\n\n```\n\u002F\u002F app\u002Fusers\u002Fpage.tsx (Server Component)\nimport { createCaller } from '..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002Fserver\u002Fcontext';\n\nexport default async function UsersPage() {\n  const ctx = await createContext({ req: null, res: null });\n  const caller = createCaller(ctx);\n\n  const users = await caller.user.list();\n\n  return (\n    \u003Cdiv>\n      {users.map((user) => (\n        \u003Cdiv key={user.id}>{user.name}\u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Server Actions\n\n```\n\u002F\u002F app\u002Factions.ts\n'use server';\n\nimport { createCaller } from '..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002Fserver\u002Fcontext';\n\nexport async function createPost(formData: FormData) {\n  const ctx = await createContext({ req: null, res: null });\n  const caller = createCaller(ctx);\n\n  return caller.post.create({\n    title: formData.get('title') as string,\n    content: formData.get('content') as string,\n  });\n}\n```\n\n### App Router Provider\n\n```\n\u002F\u002F app\u002Fproviders.tsx\n'use client';\n\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '.\u002Ftrpc';\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  const [queryClient] = useState(() => new QueryClient());\n  const [trpcClient] = useState(() =>\n    trpc.createClient({\n      links: [\n        httpBatchLink({\n          url: '\u002Fapi\u002Ftrpc',\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        {children}\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n\n\u002F\u002F app\u002Flayout.tsx\nimport { Providers } from '.\u002Fproviders';\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        \u003CProviders>{children}\u003C\u002FProviders>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### Client Components in App Router\n\n```\n\u002F\u002F app\u002Fposts\u002Fcreate-button.tsx\n'use client';\n\nimport { trpc } from '..\u002Ftrpc';\n\nexport function CreatePostButton() {\n  const createPost = trpc.post.create.useMutation();\n\n  return (\n    \u003Cbutton onClick={() => createPost.mutate({ title: 'New Post' })}>\n      Create Post\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n### API Route Handler (App Router)\n\n```\n\u002F\u002F app\u002Fapi\u002Ftrpc\u002F[trpc]\u002Froute.ts\nimport { fetchRequestHandler } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { appRouter } from '..\u002F..\u002F..\u002F..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002F..\u002F..\u002F..\u002Fserver\u002Fcontext';\n\nconst handler = (req: Request) =>\n  fetchRequestHandler({\n    endpoint: '\u002Fapi\u002Ftrpc',\n    req,\n    router: appRouter,\n    createContext,\n  });\n\nexport { handler as GET, handler as POST };\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Real-time Subscriptions\n\n### WebSocket Setup (Server)\n\n```\nimport { applyWSSHandler } from '@trpc\u002Fserver\u002Fadapters\u002Fws';\nimport ws from 'ws';\n\nconst wss = new ws.Server({ port: 3001 });\n\napplyWSSHandler({\n  wss,\n  router: appRouter,\n  createContext,\n});\n\nconsole.log('WebSocket server listening on port 3001');\n```\n\n### Subscription Procedure\n\n```\nimport { observable } from '@trpc\u002Fserver\u002Fobservable';\nimport { EventEmitter } from 'events';\n\nconst ee = new EventEmitter();\n\nconst router = t.router({\n  onPostAdd: t.procedure.subscription(() => {\n    return observable\u003CPost>((emit) => {\n      const onAdd = (data: Post) => emit.next(data);\n\n      ee.on('add', onAdd);\n\n      return () => {\n        ee.off('add', onAdd);\n      };\n    });\n  }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(({ input }) => {\n      const post = { id: Date.now().toString(), ...input };\n      ee.emit('add', post); \u002F\u002F Emit to subscribers\n      return post;\n    }),\n});\n```\n\n### Client WebSocket Setup\n\n```\nimport { createWSClient, wsLink } from '@trpc\u002Fclient';\n\nconst wsClient = createWSClient({\n  url: 'ws:\u002F\u002Flocalhost:3001',\n});\n\nconst trpcClient = trpc.createClient({\n  links: [\n    wsLink({\n      client: wsClient,\n    }),\n  ],\n});\n```\n\n### useSubscription Hook\n\n```\nfunction PostFeed() {\n  const [posts, setPosts] = useState\u003CPost[]>([]);\n\n  trpc.onPostAdd.useSubscription(undefined, {\n    onData: (post) => {\n      setPosts((prev) => [post, ...prev]);\n    },\n    onError: (err) => {\n      console.error('Subscription error:', err);\n    },\n  });\n\n  return (\n    \u003Cdiv>\n      {posts.map((post) => (\n        \u003CPostCard key={post.id} post={post} \u002F>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Subscription with Input\n\n```\n\u002F\u002F Server\nconst router = t.router({\n  onUserStatusChange: t.procedure\n    .input(z.string())\n    .subscription(({ input }) => {\n      return observable\u003CUserStatus>((emit) => {\n        const onChange = (userId: string, status: UserStatus) => {\n          if (userId === input) {\n            emit.next(status);\n          }\n        };\n\n        ee.on('statusChange', onChange);\n        return () => ee.off('statusChange', onChange);\n      });\n    }),\n});\n\n\u002F\u002F Client\ntrpc.onUserStatusChange.useSubscription('user-123', {\n  onData: (status) => console.log('Status:', status),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## File Uploads\n\n### Multipart Form Data (Server)\n\n```\n\u002F\u002F Next.js API route with file upload\nimport { NextApiRequest, NextApiResponse } from 'next';\nimport formidable from 'formidable';\nimport fs from 'fs';\n\nexport const config = {\n  api: { bodyParser: false },\n};\n\nexport default async function handler(req: NextApiRequest, res: NextApiResponse) {\n  const form = formidable({ multiples: false });\n\n  form.parse(req, async (err, fields, files) => {\n    if (err) return res.status(500).json({ error: 'Upload failed' });\n\n    const file = files.file as formidable.File;\n    const buffer = fs.readFileSync(file.filepath);\n\n    \u002F\u002F Upload to S3, etc.\n    const url = await uploadToS3(buffer, file.originalFilename);\n\n    res.json({ url });\n  });\n}\n```\n\n### Base64 Upload (tRPC)\n\n```\n\u002F\u002F For small files only (\u003C1MB)\nconst router = t.router({\n  uploadAvatar: t.procedure\n    .input(z.object({\n      fileName: z.string(),\n      fileData: z.string(), \u002F\u002F Base64\n    }))\n    .mutation(async ({ input }) => {\n      const buffer = Buffer.from(input.fileData, 'base64');\n      const url = await uploadToS3(buffer, input.fileName);\n      return { url };\n    }),\n});\n\n\u002F\u002F Client\nconst uploadAvatar = trpc.uploadAvatar.useMutation();\n\nconst handleFileChange = (e: React.ChangeEvent\u003CHTMLInputElement>) => {\n  const file = e.target.files?.[0];\n  if (!file) return;\n\n  const reader = new FileReader();\n  reader.onload = () => {\n    const base64 = reader.result as string;\n    uploadAvatar.mutate({\n      fileName: file.name,\n      fileData: base64.split(',')[1], \u002F\u002F Remove data:image\u002F...;base64,\n    });\n  };\n  reader.readAsDataURL(file);\n};\n```\n\n### Signed URL Pattern (Recommended)\n\n```\n\u002F\u002F Step 1: Get signed upload URL from tRPC\nconst router = t.router({\n  getUploadUrl: t.procedure\n    .input(z.object({\n      fileName: z.string(),\n      fileType: z.string(),\n    }))\n    .mutation(async ({ input }) => {\n      const signedUrl = await s3.getSignedUrl('putObject', {\n        Bucket: 'my-bucket',\n        Key: input.fileName,\n        ContentType: input.fileType,\n        Expires: 60, \u002F\u002F 1 minute\n      });\n\n      return { uploadUrl: signedUrl, fileUrl: `https:\u002F\u002Fcdn.example.com\u002F${input.fileName}` };\n    }),\n});\n\n\u002F\u002F Step 2: Client uploads directly to S3\nasync function uploadFile(file: File) {\n  \u002F\u002F Get signed URL\n  const { uploadUrl, fileUrl } = await trpc.getUploadUrl.mutate({\n    fileName: file.name,\n    fileType: file.type,\n  });\n\n  \u002F\u002F Upload directly to S3\n  await fetch(uploadUrl, {\n    method: 'PUT',\n    body: file,\n    headers: { 'Content-Type': file.type },\n  });\n\n  \u002F\u002F Save file URL to database via tRPC\n  await trpc.user.updateAvatar.mutate({ url: fileUrl });\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Batch Requests & Data Loaders\n\n### Automatic Batching\n\n```\n\u002F\u002F Client configuration\nconst trpcClient = trpc.createClient({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      maxBatchSize: 10, \u002F\u002F Batch up to 10 requests\n    }),\n  ],\n});\n\n\u002F\u002F Multiple calls made close together are batched into one HTTP request\nconst user1 = trpc.user.getById.useQuery('1');\nconst user2 = trpc.user.getById.useQuery('2');\nconst user3 = trpc.user.getById.useQuery('3');\n\u002F\u002F → Single HTTP request with 3 procedure calls\n```\n\n### DataLoader Pattern\n\n```\nimport DataLoader from 'dataloader';\n\n\u002F\u002F Create DataLoader in context\nexport async function createContext() {\n  const userLoader = new DataLoader(async (ids: readonly string[]) => {\n    const users = await db.user.findMany({\n      where: { id: { in: [...ids] } },\n    });\n\n    \u002F\u002F Return in same order as input\n    return ids.map((id) => users.find((u) => u.id === id));\n  });\n\n  return { userLoader };\n}\n\n\u002F\u002F Use in procedures\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ ctx, input }) => {\n      return ctx.userLoader.load(input); \u002F\u002F Batched!\n    }),\n\n  getPosts: t.procedure.query(async ({ ctx }) => {\n    const posts = await db.post.findMany({ take: 10 });\n\n    \u002F\u002F N+1 problem solved—all authors fetched in one query\n    const postsWithAuthors = await Promise.all(\n      posts.map(async (post) => ({\n        ...post,\n        author: await ctx.userLoader.load(post.authorId),\n      }))\n    );\n\n    return postsWithAuthors;\n  }),\n});\n```\n\n### Conditional Batching\n\n```\nimport { httpBatchLink, httpLink, splitLink } from '@trpc\u002Fclient';\n\nconst trpcClient = trpc.createClient({\n  links: [\n    splitLink({\n      \u002F\u002F Batch queries, don't batch mutations\n      condition: (op) => op.type === 'query',\n      true: httpBatchLink({ url: '\u002Fapi\u002Ftrpc' }),\n      false: httpLink({ url: '\u002Fapi\u002Ftrpc' }),\n    }),\n  ],\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## TypeScript Inference Patterns\n\n### Inferring Types from Router\n\n```\nimport type { inferRouterInputs, inferRouterOutputs } from '@trpc\u002Fserver';\nimport type { AppRouter } from '.\u002Fserver';\n\n\u002F\u002F Input types\ntype RouterInputs = inferRouterInputs\u003CAppRouter>;\ntype CreateUserInput = RouterInputs['user']['create'];\n\n\u002F\u002F Output types\ntype RouterOutputs = inferRouterOutputs\u003CAppRouter>;\ntype User = RouterOutputs['user']['getById'];\n\n\u002F\u002F Use in components\nfunction UserCard({ user }: { user: User }) {\n  return \u003Cdiv>{user.name}\u003C\u002Fdiv>;\n}\n```\n\n### Procedure Helpers\n\n```\nimport type { inferProcedureInput, inferProcedureOutput } from '@trpc\u002Fserver';\n\ntype CreatePostInput = inferProcedureInput\u003CAppRouter['post']['create']>;\ntype Post = inferProcedureOutput\u003CAppRouter['post']['getById']>;\n```\n\n### Context Type Inference\n\n```\nimport { inferAsyncReturnType } from '@trpc\u002Fserver';\n\nexport async function createContext() {\n  return {\n    db: prisma,\n    user: null as User | null,\n  };\n}\n\nexport type Context = inferAsyncReturnType\u003Ctypeof createContext>;\n\nconst t = initTRPC.context\u003CContext>().create();\n```\n\n### Generic Procedures\n\n```\n\u002F\u002F Reusable pagination\nfunction createPaginatedProcedure\u003CT>(\n  getData: (cursor: number, limit: number) => Promise\u003CT[]>\n) {\n  return t.procedure\n    .input(z.object({\n      cursor: z.number().optional(),\n      limit: z.number().default(10),\n    }))\n    .query(async ({ input }) => {\n      const items = await getData(input.cursor ?? 0, input.limit);\n      return {\n        items,\n        nextCursor: items.length === input.limit\n          ? (input.cursor ?? 0) + input.limit\n          : undefined,\n      };\n    });\n}\n\nconst router = t.router({\n  posts: createPaginatedProcedure((cursor, limit) =>\n    db.post.findMany({ skip: cursor, take: limit })\n  ),\n  users: createPaginatedProcedure((cursor, limit) =>\n    db.user.findMany({ skip: cursor, take: limit })\n  ),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Testing Strategies\n\n### Unit Testing Procedures\n\n```\nimport { createCaller } from '..\u002Frouters';\n\ndescribe('User Router', () => {\n  it('should create user', async () => {\n    const ctx = {\n      db: mockDb,\n      session: null,\n    };\n\n    const caller = createCaller(ctx);\n\n    const result = await caller.user.create({\n      name: 'Alice',\n      email: 'alice@example.com',\n    });\n\n    expect(result).toMatchObject({\n      name: 'Alice',\n      email: 'alice@example.com',\n    });\n  });\n});\n```\n\n### Integration Testing\n\n```\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { createTRPCProxyClient } from '@trpc\u002Fclient';\nimport type { AppRouter } from '..\u002Fserver';\n\ndescribe('tRPC Integration', () => {\n  const client = createTRPCProxyClient\u003CAppRouter>({\n    links: [\n      httpBatchLink({\n        url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      }),\n    ],\n  });\n\n  it('should fetch user', async () => {\n    const user = await client.user.getById.query('123');\n    expect(user.id).toBe('123');\n  });\n});\n```\n\n### Mocking Context\n\n```\nimport { createCaller } from '..\u002Frouters';\n\nconst mockContext = {\n  db: {\n    user: {\n      findUnique: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),\n      create: vi.fn(),\n    },\n  },\n  session: {\n    user: { id: '1', email: 'alice@example.com' },\n  },\n};\n\nit('should get current user', async () => {\n  const caller = createCaller(mockContext);\n  const user = await caller.user.getMe();\n\n  expect(mockContext.db.user.findUnique).toHaveBeenCalledWith({\n    where: { id: '1' },\n  });\n  expect(user.name).toBe('Alice');\n});\n```\n\n### Testing React Hooks\n\n```\nimport { renderHook, waitFor } from '@testing-library\u002Freact';\nimport { createWrapper } from '.\u002Ftest-utils';\n\nit('should fetch posts', async () => {\n  const { result } = renderHook(() => trpc.post.list.useQuery(), {\n    wrapper: createWrapper(),\n  });\n\n  await waitFor(() => expect(result.current.isSuccess).toBe(true));\n\n  expect(result.current.data).toHaveLength(10);\n});\n\n\u002F\u002F test-utils.ts\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { trpc } from '..\u002Futils\u002Ftrpc';\n\nexport function createWrapper() {\n  const queryClient = new QueryClient();\n  const trpcClient = trpc.createClient({\n    links: [httpBatchLink({ url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc' })],\n  });\n\n  return ({ children }) => (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        {children}\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Production Patterns\n\n### Error Monitoring\n\n```\nimport * as Sentry from '@sentry\u002Fnode';\n\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    \u002F\u002F Log to Sentry\n    if (error.code === 'INTERNAL_SERVER_ERROR') {\n      Sentry.captureException(error);\n    }\n\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        \u002F\u002F Don't expose internal errors in production\n        message: process.env.NODE_ENV === 'production' && error.code === 'INTERNAL_SERVER_ERROR'\n          ? 'Internal server error'\n          : shape.message,\n      },\n    };\n  },\n});\n```\n\n### Rate Limiting\n\n```\nimport { Ratelimit } from '@upstash\u002Fratelimit';\nimport { Redis } from '@upstash\u002Fredis';\n\nconst ratelimit = new Ratelimit({\n  redis: Redis.fromEnv(),\n  limiter: Ratelimit.slidingWindow(10, '10 s'),\n});\n\nconst rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {\n  const identifier = ctx.session?.user?.id ?? ctx.req.ip;\n  const { success } = await ratelimit.limit(identifier);\n\n  if (!success) {\n    throw new TRPCError({\n      code: 'TOO_MANY_REQUESTS',\n      message: 'Rate limit exceeded',\n    });\n  }\n\n  return next();\n});\n```\n\n### Caching\n\n```\nimport { Redis } from 'ioredis';\n\nconst redis = new Redis(process.env.REDIS_URL);\n\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(async ({ input }) => {\n      \u002F\u002F Check cache\n      const cached = await redis.get(`user:${input}`);\n      if (cached) return JSON.parse(cached);\n\n      \u002F\u002F Fetch from database\n      const user = await db.user.findUnique({ where: { id: input } });\n\n      \u002F\u002F Cache for 5 minutes\n      await redis.setex(`user:${input}`, 300, JSON.stringify(user));\n\n      return user;\n    }),\n});\n```\n\n### Request Logging\n\n```\nconst loggingMiddleware = t.middleware(async ({ path, type, next, input }) => {\n  const start = Date.now();\n\n  console.log(`→ ${type} ${path}`, { input });\n\n  try {\n    const result = await next();\n    const duration = Date.now() - start;\n    console.log(`✓ ${type} ${path} - ${duration}ms`);\n    return result;\n  } catch (error) {\n    const duration = Date.now() - start;\n    console.error(`✗ ${type} ${path} - ${duration}ms`, { error });\n    throw error;\n  }\n});\n```\n\n### OpenTelemetry Integration\n\n```\nimport { trace } from '@opentelemetry\u002Fapi';\n\nconst tracingMiddleware = t.middleware(async ({ path, type, next }) => {\n  const tracer = trace.getTracer('trpc');\n\n  return tracer.startActiveSpan(`trpc.${type}.${path}`, async (span) => {\n    try {\n      const result = await next();\n      span.setStatus({ code: 0 }); \u002F\u002F OK\n      return result;\n    } catch (error) {\n      span.setStatus({ code: 2, message: error.message }); \u002F\u002F ERROR\n      span.recordException(error);\n      throw error;\n    } finally {\n      span.end();\n    }\n  });\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Comparison with REST & GraphQL\n\n### Feature Comparison\n\n| Feature | tRPC | REST | GraphQL |\n| --- | --- | --- | --- |\n| **Type Safety** | Full (TypeScript) | Manual\u002Fcodegen | Manual\u002Fcodegen |\n| **Code Generation** | None | Optional (OpenAPI) | Required |\n| **Learning Curve** | Low | Low | Medium\u002FHigh |\n| **Client Libraries** | TypeScript only | Any language | Any language |\n| **API Documentation** | TypeScript types | OpenAPI\u002FSwagger | Schema\u002Fintrospection |\n| **Public APIs** | ❌ No | ✅ Yes | ✅ Yes |\n| **Flexible Queries** | ❌ Fixed | ❌ Fixed | ✅ Yes |\n| **Overfetching** | Minimal | Common | None |\n| **Caching** | React Query | HTTP caching | Complex |\n| **Real-time** | WebSocket | SSE\u002FWebSocket | Subscriptions |\n| **File Uploads** | Workarounds | Native | Complex |\n\n### When to Choose Each\n\n**tRPC**:\n\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n\n**REST**:\n\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n\n**GraphQL**:\n\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n\n### Migration Path\n\ntRPC can **coexist** with REST\u002FGraphQL:\n\n```\n\u002F\u002F Use tRPC for internal, REST for public\nconst router = t.router({\n  internal: internalRouter, \u002F\u002F tRPC only\n});\n\n\u002F\u002F Expose REST endpoints separately\napp.get('\u002Fapi\u002Fpublic\u002Fusers', publicRestHandler);\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Migration from REST\n\n### Gradual Migration Strategy\n\n1. **Add tRPC alongside REST**: Don't rewrite everything at once\n2. **New features in tRPC**: Start with new endpoints\n3. **Migrate high-value endpoints**: Focus on complex or frequently used APIs\n4. **Keep public APIs in REST**: Only migrate internal consumption\n\n### Converting REST to tRPC\n\n**Before (REST)**:\n\n```\n\u002F\u002F pages\u002Fapi\u002Fusers\u002F[id].ts\nexport default async function handler(req, res) {\n  if (req.method === 'GET') {\n    const user = await db.user.findUnique({ where: { id: req.query.id } });\n    res.json(user);\n  } else if (req.method === 'PATCH') {\n    const user = await db.user.update({\n      where: { id: req.query.id },\n      data: req.body,\n    });\n    res.json(user);\n  }\n}\n\n\u002F\u002F Client\nconst response = await fetch(`\u002Fapi\u002Fusers\u002F${id}`);\nconst user = await response.json(); \u002F\u002F No types!\n```\n\n**After (tRPC)**:\n\n```\n\u002F\u002F server\u002Frouters\u002Fuser.ts\nexport const userRouter = t.router({\n  getById: t.procedure\n    .input(z.string())\n    .query(({ input }) => db.user.findUnique({ where: { id: input } })),\n\n  update: t.procedure\n    .input(z.object({\n      id: z.string(),\n      data: z.object({ name: z.string().optional() }),\n    }))\n    .mutation(({ input }) => db.user.update({\n      where: { id: input.id },\n      data: input.data,\n    })),\n});\n\n\u002F\u002F Client\nconst user = await trpc.user.getById.query(id); \u002F\u002F Fully typed!\n```\n\n### Shared Validation\n\n```\n\u002F\u002F Reuse Zod schemas across REST and tRPC during migration\nimport { createUserSchema } from '..\u002Fschemas\u002Fuser';\n\n\u002F\u002F tRPC\nconst router = t.router({\n  createUser: t.procedure\n    .input(createUserSchema)\n    .mutation(({ input }) => createUser(input)),\n});\n\n\u002F\u002F REST (validate with same schema)\nexport default async function handler(req, res) {\n  const parsed = createUserSchema.safeParse(req.body);\n  if (!parsed.success) {\n    return res.status(400).json({ errors: parsed.error });\n  }\n  const user = await createUser(parsed.data);\n  res.json(user);\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Best Practices & Performance\n\n### Code Organization\n\n```\nserver\u002F\n├── trpc.ts              # tRPC instance, base procedures\n├── context.ts           # Context creation\n├── middleware\u002F\n│   ├── auth.ts          # Authentication middleware\n│   ├── logging.ts       # Logging middleware\n│   └── rateLimit.ts     # Rate limiting\n├── routers\u002F\n│   ├── _app.ts          # Root router\n│   ├── user.ts          # User procedures\n│   ├── post.ts          # Post procedures\n│   └── admin\u002F\n│       └── index.ts     # Admin-only procedures\n└── schemas\u002F\n    ├── user.ts          # User Zod schemas\n    └── post.ts          # Post Zod schemas\n```\n\n### Performance Tips\n\n1. **Use batching for multiple queries**:\n\n   ```\n   httpBatchLink({ url: '\u002Fapi\u002Ftrpc', maxBatchSize: 10 })\n   ```\n2. **Implement DataLoader for N+1 queries**:\n\n   ```\n   const userLoader = new DataLoader(batchLoadUsers);\n   ```\n3. **Cache expensive queries**:\n\n   ```\n   trpc.posts.list.useQuery(undefined, { staleTime: 5 * 60 * 1000 });\n   ```\n4. **Optimize database queries**:\n\n   ```\n   \u002F\u002F ❌ Bad: N+1 query\n   const posts = await db.post.findMany();\n   const postsWithAuthors = await Promise.all(\n     posts.map((p) => db.user.findUnique({ where: { id: p.authorId } }))\n   );\n\n   \u002F\u002F ✅ Good: Single query with include\n   const posts = await db.post.findMany({\n     include: { author: true },\n   });\n   ```\n5. **Use React Query's deduplication**:\n\n   ```\n   \u002F\u002F Multiple components can call same query—React Query deduplicates\n   const { data } = trpc.user.getMe.useQuery();\n   ```\n\n### Security Best Practices\n\n1. **Always validate input with Zod**\n2. **Use middleware for authentication**:\n\n   ```\n   const protectedProcedure = t.procedure.use(isAuthed);\n   ```\n3. **Sanitize error messages in production**\n4. **Implement rate limiting**\n5. **Use HTTPS in production**\n6. **Set CORS properly**:\n\n   ```\n   createNextApiHandler({\n     router: appRouter,\n     createContext,\n     onError: ({ error }) => {\n       if (error.code === 'INTERNAL_SERVER_ERROR') {\n         console.error('Internal error:', error);\n       }\n     },\n   });\n   ```\n\n### Type Safety Tips\n\n1. **Export router type, not implementation**:\n\n   ```\n   export type AppRouter = typeof appRouter; \u002F\u002F ✅\n   \u002F\u002F Don't export `appRouter` itself to client\n   ```\n2. **Use `satisfies` for better inference**:\n\n   ```\n   const input = {\n     name: 'Alice',\n     age: 30,\n   } satisfies CreateUserInput;\n   ```\n3. **Avoid `any` in context**:\n\n   ```\n   \u002F\u002F ❌ Bad\n   ctx: { user: any }\n\n   \u002F\u002F ✅ Good\n   ctx: { user: User | null }\n   ```\n\n### Development Workflow\n\n1. **Define schema first**: Write Zod schemas before procedures\n2. **Test procedures in isolation**: Use `createCaller` for unit tests\n3. **Use TypeScript strict mode**: Catch type errors early\n4. **Enable React Query DevTools**:\n\n   ```\n   import { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools';\n\n   \u003CReactQueryDevtools initialIsOpen={false} \u002F>\n   ```\n\n### Common Pitfalls\n\n❌ **Don't return sensitive data**:\n\n```\n\u002F\u002F Bad: Exposes password hash\n.query(() => db.user.findMany())\n\n\u002F\u002F Good: Select specific fields\n.query(() => db.user.findMany({ select: { id: true, name: true } }))\n```\n\n❌ **Don't use mutations for reads**:\n\n```\n\u002F\u002F Bad: Side-effect-free operation as mutation\ngetMostRecentPost: t.procedure.mutation(() => getPost())\n\n\u002F\u002F Good: Use query for reads\ngetMostRecentPost: t.procedure.query(() => getPost())\n```\n\n❌ **Don't skip input validation**:\n\n```\n\u002F\u002F Bad: No validation\n.input(z.any())\n\n\u002F\u002F Good: Strict validation\n.input(z.object({ id: z.string().uuid() }))\n```\n\n### Monitoring & Observability\n\n```\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    \u002F\u002F Log metrics\n    metrics.increment('trpc.error', { code: error.code });\n\n    \u002F\u002F Send to error tracking\n    if (error.code === 'INTERNAL_SERVER_ERROR') {\n      Sentry.captureException(error);\n    }\n\n    return shape;\n  },\n});\n\nconst loggingMiddleware = t.middleware(async ({ path, type, next }) => {\n  const start = Date.now();\n  const result = await next();\n\n  \u002F\u002F Log performance metrics\n  metrics.timing('trpc.duration', Date.now() - start, { path, type });\n\n  return result;\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Summary\n\n**tRPC** enables type-safe APIs with minimal boilerplate:\n\n- ✅ **No code generation**: Types inferred from TypeScript\n- ✅ **React Query integration**: Built-in caching and optimistic updates\n- ✅ **Next.js first-class support**: App Router, Server Components\n- ✅ **Developer experience**: Auto-complete, refactoring, type errors\n\n**Best for**: Full-stack TypeScript apps, Next.js projects, internal tools\n**Avoid for**: Public APIs, multi-language services\n\n**Get Started**: Install → Define router → Use in client → Enjoy type safety!\n\n**Related Skills**: Zod (validation), React Query (caching), Next.js (integration)\n\n","description: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Summary\n\n- Shares TypeScript types between client and server automatically—no codegen required, with full type inference across queries, mutations, and subscriptions\n- Integrates seamlessly with React Query for caching, optimistic updates, and infinite queries; supports Next.js App Router, Server Components, and WebSocket subscriptions\n- Validates all inputs with Zod schemas; includes middleware for authentication, logging, and rate limiting; error handling with typed TRPCError codes\n- Supports batching, DataLoader patterns, file uploads via signed URLs, and production patterns like caching, monitoring, and OpenTelemetry tracing\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n- tRPC: Full-stack TypeScript, monorepo, internal tools\n- REST: Public APIs, language-agnostic, broad compatibility\n- GraphQL: Complex data graphs, multiple clients, flexible queries\n- Router: Collection of procedures (API endpoints)\n- Procedure: Single API operation (query or mutation)\n- Context: Request-scoped data (user, database, etc.)\n- Middleware: Intercept\u002Fmodify requests (auth, logging)\n- Input\u002FOutput: Validated with Zod schemas\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n- Add tRPC alongside REST: Don't rewrite everything at once\n- New features in tRPC: Start with new endpoints\n- Migrate high-value endpoints: Focus on complex or frequently used APIs\n- Keep public APIs in REST: Only migrate internal consumption\n- Use batching for multiple queries:httpBatchLink({url:'\u002Fapi\u002Ftrpc',maxBatchSize:10})\n- Implement DataLoader for N+1 queries:constuserLoader=newDataLoader(batchLoadUsers);\n- Cache expensive queries:trpc.posts.list.useQuery(undefined,{staleTime:5*60*1000});\n- Optimize database queries:\u002F\u002F ❌ Bad: N+1 queryconstposts=awaitdb.post.findMany();constpostsWithAuthors=awaitPromise.all(posts.map((p)=>db.user.findUnique({where:{id:p.authorId}})));\u002F\u002F ✅ Good: Single query with includeconstposts=awaitdb.post.findMany({include:{author:true},});\n- Use React Query's deduplication:\u002F\u002F Multiple components can call same query—React Query deduplicatesconst{data}=trpc.user.getMe.useQuery();\n- Always validate input with Zod\n- Use middleware for authentication:constprotectedProcedure=t.procedure.use(isAuthed);\n- Sanitize error messages in production\n- Implement rate limiting\n- Use HTTPS in production\n- Set CORS properly:createNextApiHandler({router:appRouter,createContext,onError:({error})=>{if(error.code==='INTERNAL_SERVER_ERROR'){console.error('Internal error:',error);}},});\n- Export router type, not implementation:exporttypeAppRouter=typeofappRouter;\u002F\u002F ✅\u002F\u002F Don't export `appRouter` itself to client\n- Usesatisfiesfor better inference:constinput={name:'Alice',age:30,}satisfies CreateUserInput;\n- Avoidanyin context:\u002F\u002F ❌ Badctx:{user:any}\u002F\u002F ✅ Goodctx:{user:User|null}\n- Define schema first: Write Zod schemas before procedures\n- Test procedures in isolation: UsecreateCallerfor unit tests\n- Use TypeScript strict mode: Catch type errors early\n- Enable React Query DevTools:import{ReactQueryDevtools}from'@tanstack\u002Freact-query-devtools';\u003CReactQueryDevtools initialIsOpen={false}\u002F>\n- ✅No code generation: Types inferred from TypeScript\n- ✅React Query integration: Built-in caching and optimistic updates\n- ✅Next.js first-class support: App Router, Server Components\n- ✅Developer experience: Auto-complete, refactoring, type errors\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n- tRPC: Full-stack TypeScript, monorepo, internal tools\n- REST: Public APIs, language-agnostic, broad compatibility\n- GraphQL: Complex data graphs, multiple clients, flexible queries\n- Router: Collection of procedures (API endpoints)\n- Procedure: Single API operation (query or mutation)\n- Context: Request-scoped data (user, database, etc.)\n- Middleware: Intercept\u002Fmodify requests (auth, logging)\n- Input\u002FOutput: Validated with Zod schemas\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n- Add tRPC alongside REST: Don't rewrite everything at once\n- New features in tRPC: Start with new endpoints\n- Migrate high-value endpoints: Focus on complex or frequently used APIs\n- Keep public APIs in REST: Only migrate internal consumption\n- Use batching for multiple queries:httpBatchLink({url:'\u002Fapi\u002Ftrpc',maxBatchSize:10})\n- Implement DataLoader for N+1 queries:constuserLoader=newDataLoader(batchLoadUsers);\n- Cache expensive queries:trpc.posts.list.useQuery(undefined,{staleTime:5*60*1000});\n- Optimize database queries:\u002F\u002F ❌ Bad: N+1 queryconstposts=awaitdb.post.findMany();constpostsWithAuthors=awaitPromise.all(posts.map((p)=>db.user.findUnique({where:{id:p.authorId}})));\u002F\u002F ✅ Good: Single query with includeconstposts=awaitdb.post.findMany({include:{author:true},});\n- Use React Query's deduplication:\u002F\u002F Multiple components can call same query—React Query deduplicatesconst{data}=trpc.user.getMe.useQuery();\n- Always validate input with Zod\n- Use middleware for authentication:constprotectedProcedure=t.procedure.use(isAuthed);\n- Sanitize error messages in production\n- Implement rate limiting\n- Use HTTPS in production\n- Set CORS properly:createNextApiHandler({router:appRouter,createContext,onError:({error})=>{if(error.code==='INTERNAL_SERVER_ERROR'){console.error('Internal error:',error);}},});\n- Export router type, not implementation:exporttypeAppRouter=typeofappRouter;\u002F\u002F ✅\u002F\u002F Don't export `appRouter` itself to client\n- Usesatisfiesfor better inference:constinput={name:'Alice',age:30,}satisfies CreateUserInput;\n- Avoidanyin context:\u002F\u002F ❌ Badctx:{user:any}\u002F\u002F ✅ Goodctx:{user:User|null}\n- Define schema first: Write Zod schemas before procedures\n- Test procedures in isolation: UsecreateCallerfor unit tests\n- Use TypeScript strict mode: Catch type errors early\n- Enable React Query DevTools:import{ReactQueryDevtools}from'@tanstack\u002Freact-query-devtools';\u003CReactQueryDevtools initialIsOpen={false}\u002F>\n- ✅No code generation: Types inferred from TypeScript\n- ✅React Query integration: Built-in caching and optimistic updates\n- ✅Next.js first-class support: App Router, Server Components\n- ✅Developer experience: Auto-complete, refactoring, type errors\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## SKILL.md\n\n# tRPC - End-to-End Type Safety\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## progressive\\_disclosure: entry\\_point: summary sections: - id: summary title: \"tRPC Overview\" tokens: 70 next: [when\\_to\\_use, quick\\_start] - id: when\\_to\\_use title: \"When to Use tRPC\" tokens: 150 next: [quick\\_start, core\\_concepts] - id: quick\\_start title: \"Quick Start\" tokens: 300 next: [core\\_concepts, router\\_definition] - id: core\\_concepts title: \"Core Concepts\" tokens: 400 next: [router\\_definition, procedures] - id: router\\_definition title: \"Router Definition\" tokens: 350 next: [procedures, context] - id: procedures title: \"Procedures (Query & Mutation)\" tokens: 400 next: [input\\_validation, context] - id: input\\_validation title: \"Input Validation with Zod\" tokens: 350 next: [context, middleware] - id: context title: \"Context Management\" tokens: 400 next: [middleware, error\\_handling] - id: middleware title: \"Middleware\" tokens: 400 next: [error\\_handling, client\\_setup] - id: error\\_handling title: \"Error Handling\" tokens: 350 next: [client\\_setup, react\\_integration] - id: client\\_setup title: \"Client Setup\" tokens: 400 next: [react\\_integration, nextjs\\_integration] - id: react\\_integration title: \"React Query Integration\" tokens: 450 next: [nextjs\\_integration, subscriptions] - id: nextjs\\_integration title: \"Next.js App Router Integration\" tokens: 500 next: [subscriptions, file\\_uploads] - id: subscriptions title: \"Real-time Subscriptions\" tokens: 400 next: [file\\_uploads, batching] - id: file\\_uploads title: \"File Uploads\" tokens: 300 next: [batching, typescript\\_inference] - id: batching title: \"Batch Requests & Data Loaders\" tokens: 350 next: [typescript\\_inference, testing] - id: typescript\\_inference title: \"TypeScript Inference Patterns\" tokens: 300 next: [testing, production\\_patterns] - id: testing title: \"Testing Strategies\" tokens: 400 next: [production\\_patterns, comparison] - id: production\\_patterns title: \"Production Patterns\" tokens: 450 next: [comparison, migration] - id: comparison title: \"Comparison with REST & GraphQL\" tokens: 250 next: [migration, best\\_practices] - id: migration title: \"Migration from REST\" tokens: 300 next: [best\\_practices] - id: best\\_practices title: \"Best Practices & Performance\" tokens: 400\n\n## Summary\n\n**tRPC** enables end-to-end type safety between TypeScript clients and servers without code generation. Define your API once, get automatic type inference everywhere.\n\n**Key Benefits**: Zero codegen, TypeScript inference, React Query integration, minimal boilerplate.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## When to Use tRPC\n\n**✅ Perfect For**:\n\n- Full-stack TypeScript applications (Next.js, T3 stack)\n- Projects where client and server share TypeScript codebase\n- Teams wanting REST-like simplicity with GraphQL-like type safety\n- Apps using React Query for data fetching\n- Internal APIs where you control both client and server\n\n**❌ Avoid When**:\n\n- Public APIs consumed by non-TypeScript clients\n- Microservices in different languages\n- Mobile apps using Swift\u002FKotlin (use REST\u002FGraphQL instead)\n- Need API documentation for external developers (OpenAPI better)\n\n**When to Choose**:\n\n- **tRPC**: Full-stack TypeScript, monorepo, internal tools\n- **REST**: Public APIs, language-agnostic, broad compatibility\n- **GraphQL**: Complex data graphs, multiple clients, flexible queries\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Quick Start\n\n### Installation\n\n```\n# Server dependencies\nnpm install @trpc\u002Fserver zod\n\n# React\u002FNext.js client dependencies\nnpm install @trpc\u002Fclient @trpc\u002Freact-query @tanstack\u002Freact-query\n```\n\n### Define Router (Server)\n\n```\n\u002F\u002F server\u002Ftrpc.ts\nimport { initTRPC } from '@trpc\u002Fserver';\nimport { z } from 'zod';\n\nconst t = initTRPC.create();\n\nexport const appRouter = t.router({\n  hello: t.procedure\n    .input(z.object({ name: z.string() }))\n    .query(({ input }) => {\n      return { greeting: `Hello ${input.name}` };\n    }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string(), content: z.string() }))\n    .mutation(async ({ input }) => {\n      \u002F\u002F Save to database\n      return { id: 1, ...input };\n    }),\n});\n\nexport type AppRouter = typeof appRouter;\n```\n\n### Use in Client (React)\n\n```\n\u002F\u002F client\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '..\u002Fserver\u002Ftrpc';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n\n\u002F\u002F Component\nfunction MyComponent() {\n  const { data } = trpc.hello.useQuery({ name: 'World' });\n  const createPost = trpc.createPost.useMutation();\n\n  return \u003Cdiv>{data?.greeting}\u003C\u002Fdiv>; \u002F\u002F Fully typed!\n}\n```\n\n**Next**: Learn core concepts or dive into router definition.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Core Concepts\n\n### The tRPC Philosophy\n\ntRPC provides **type-safe remote procedure calls** by sharing TypeScript types between client and server. No code generation—just TypeScript's inference.\n\n### Key Components\n\n1. **Router**: Collection of procedures (API endpoints)\n2. **Procedure**: Single API operation (query or mutation)\n3. **Context**: Request-scoped data (user, database, etc.)\n4. **Middleware**: Intercept\u002Fmodify requests (auth, logging)\n5. **Input\u002FOutput**: Validated with Zod schemas\n\n### Type Flow\n\n```\n\u002F\u002F Server defines types\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ input }) => ({ id: input, name: 'Alice' })),\n});\n\n\u002F\u002F Client gets automatic types\nconst user = await trpc.getUser.query('123');\n\u002F\u002F user is typed as { id: string, name: string }\n```\n\n### Architecture Pattern\n\n```\n┌─────────────┐     Type-safe     ┌──────────────┐\n│   Client    │ ←────────────────→ │    Server    │\n│ (React)     │   No codegen!      │   (Node.js)  │\n└─────────────┘                    └──────────────┘\n      ↓                                    ↓\n React Query                          tRPC Router\n (caching)                            (procedures)\n```\n\n**Advantages**:\n\n- Changes propagate instantly (no build step)\n- Rename refactoring works across client\u002Fserver\n- Impossible to call wrong types\n- Auto-complete for all API methods\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Router Definition\n\n### Basic Router Structure\n\n```\nimport { initTRPC } from '@trpc\u002Fserver';\n\nconst t = initTRPC.create();\n\nexport const appRouter = t.router({\n  \u002F\u002F Procedures go here\n});\n\nexport type AppRouter = typeof appRouter;\n```\n\n### Nested Routers (Namespacing)\n\n```\nconst userRouter = t.router({\n  getById: t.procedure\n    .input(z.string())\n    .query(({ input }) => getUser(input)),\n\n  create: t.procedure\n    .input(z.object({ name: z.string(), email: z.string() }))\n    .mutation(({ input }) => createUser(input)),\n});\n\nconst postRouter = t.router({\n  list: t.procedure.query(() => getPosts()),\n  create: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(({ input }) => createPost(input)),\n});\n\nexport const appRouter = t.router({\n  user: userRouter,\n  post: postRouter,\n});\n\n\u002F\u002F Client usage:\n\u002F\u002F trpc.user.getById.useQuery('123')\n\u002F\u002F trpc.post.list.useQuery()\n```\n\n### Router Merging\n\n```\nimport { adminRouter } from '.\u002Fadmin';\nimport { publicRouter } from '.\u002Fpublic';\n\nexport const appRouter = t.mergeRouters(publicRouter, adminRouter);\n```\n\n### Router Organization Best Practices\n\n```\nserver\u002F\n├── trpc.ts           # tRPC instance, context, middleware\n├── routers\u002F\n│   ├── user.ts       # User-related procedures\n│   ├── post.ts       # Post-related procedures\n│   └── index.ts      # Combine all routers\n└── index.ts          # Export AppRouter type\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Procedures (Query & Mutation)\n\n### Query Procedures (Read Operations)\n\n```\nconst router = t.router({\n  \u002F\u002F Simple query\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ input }) => {\n      return db.user.findUnique({ where: { id: input } });\n    }),\n\n  \u002F\u002F Query with multiple inputs\n  searchUsers: t.procedure\n    .input(z.object({\n      query: z.string(),\n      limit: z.number().default(10),\n    }))\n    .query(({ input }) => {\n      return db.user.findMany({\n        where: { name: { contains: input.query } },\n        take: input.limit,\n      });\n    }),\n});\n```\n\n### Mutation Procedures (Write Operations)\n\n```\nconst router = t.router({\n  createUser: t.procedure\n    .input(z.object({\n      name: z.string().min(3),\n      email: z.string().email(),\n    }))\n    .mutation(async ({ input }) => {\n      return await db.user.create({ data: input });\n    }),\n\n  updateUser: t.procedure\n    .input(z.object({\n      id: z.string(),\n      data: z.object({\n        name: z.string().optional(),\n        email: z.string().email().optional(),\n      }),\n    }))\n    .mutation(async ({ input }) => {\n      return await db.user.update({\n        where: { id: input.id },\n        data: input.data,\n      });\n    }),\n});\n```\n\n### Query vs Mutation\n\n| Aspect | Query | Mutation |\n| --- | --- | --- |\n| **Purpose** | Read data | Modify data |\n| **HTTP Method** | GET | POST |\n| **Caching** | Cached by React Query | Not cached |\n| **Idempotent** | Yes | No |\n| **Side Effects** | None | Database writes, emails, etc. |\n\n### Output Typing\n\n```\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .output(z.object({ id: z.string(), name: z.string() })) \u002F\u002F Optional\n    .query(({ input }) => {\n      return { id: input, name: 'Alice' };\n    }),\n});\n```\n\n**Note**: Output validation adds runtime overhead—use for critical data only.\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Input Validation with Zod\n\n### Why Zod?\n\ntRPC uses **Zod** for runtime type validation and TypeScript inference. Zod schemas provide:\n\n- Runtime validation (prevent invalid data)\n- TypeScript types (auto-inferred from schema)\n- Transformation (parse, coerce, default values)\n\n### Basic Validation\n\n```\nimport { z } from 'zod';\n\nconst router = t.router({\n  createPost: t.procedure\n    .input(z.object({\n      title: z.string().min(5).max(100),\n      content: z.string(),\n      published: z.boolean().default(false),\n      tags: z.array(z.string()).optional(),\n    }))\n    .mutation(({ input }) => {\n      \u002F\u002F input is fully typed and validated\n      return createPost(input);\n    }),\n});\n```\n\n### Advanced Validation\n\n```\nconst createUserInput = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n  age: z.number().int().min(18),\n  role: z.enum(['user', 'admin']),\n  metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst router = t.router({\n  createUser: t.procedure\n    .input(createUserInput)\n    .mutation(({ input }) => {\n      \u002F\u002F All validation passed\n      return saveUser(input);\n    }),\n});\n```\n\n### Transformations\n\n```\nconst router = t.router({\n  getUser: t.procedure\n    .input(\n      z.object({\n        id: z.string().transform((id) => parseInt(id, 10)),\n      })\n    )\n    .query(({ input }) => {\n      \u002F\u002F input.id is now a number\n      return db.user.findUnique({ where: { id: input.id } });\n    }),\n});\n```\n\n### Reusable Schemas\n\n```\n\u002F\u002F schemas\u002Fuser.ts\nexport const CreateUserSchema = z.object({\n  name: z.string(),\n  email: z.string().email(),\n});\n\nexport const UpdateUserSchema = CreateUserSchema.partial().extend({\n  id: z.string(),\n});\n\n\u002F\u002F routers\u002Fuser.ts\nconst router = t.router({\n  create: t.procedure.input(CreateUserSchema).mutation(\u002F*...*\u002F),\n  update: t.procedure.input(UpdateUserSchema).mutation(\u002F*...*\u002F),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Context Management\n\n### What is Context?\n\n**Context** provides request-scoped data to all procedures—authentication, database connections, logging, etc.\n\n### Creating Context\n\n```\nimport { inferAsyncReturnType } from '@trpc\u002Fserver';\nimport { CreateNextContextOptions } from '@trpc\u002Fserver\u002Fadapters\u002Fnext';\n\nexport async function createContext(opts: CreateNextContextOptions) {\n  const session = await getSession(opts.req);\n\n  return {\n    session,\n    db: prisma,\n    req: opts.req,\n    res: opts.res,\n  };\n}\n\nexport type Context = inferAsyncReturnType\u003Ctypeof createContext>;\n\nconst t = initTRPC.context\u003CContext>().create();\n```\n\n### Using Context in Procedures\n\n```\nconst router = t.router({\n  getMe: t.procedure.query(({ ctx }) => {\n    if (!ctx.session?.user) {\n      throw new TRPCError({ code: 'UNAUTHORIZED' });\n    }\n\n    return ctx.db.user.findUnique({\n      where: { id: ctx.session.user.id },\n    });\n  }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(async ({ ctx, input }) => {\n      return ctx.db.post.create({\n        data: {\n          title: input.title,\n          authorId: ctx.session.user.id,\n        },\n      });\n    }),\n});\n```\n\n### Context Best Practices\n\n```\n\u002F\u002F ✅ Good: Lazy database connection\nexport async function createContext(opts: CreateNextContextOptions) {\n  return {\n    getDB: () => prisma, \u002F\u002F Lazy\n    session: await getSession(opts.req),\n  };\n}\n\n\u002F\u002F ❌ Bad: Heavy computation in context\nexport async function createContext(opts: CreateNextContextOptions) {\n  const allUsers = await prisma.user.findMany(); \u002F\u002F Too expensive!\n  return { allUsers };\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Middleware\n\n### What is Middleware?\n\nMiddleware intercepts procedure calls to add cross-cutting concerns: logging, timing, authentication, rate limiting.\n\n### Basic Middleware\n\n```\nconst loggerMiddleware = t.middleware(async ({ path, type, next }) => {\n  const start = Date.now();\n  console.log(`→ ${type} ${path}`);\n\n  const result = await next();\n\n  const duration = Date.now() - start;\n  console.log(`✓ ${type} ${path} - ${duration}ms`);\n\n  return result;\n});\n\nconst loggedProcedure = t.procedure.use(loggerMiddleware);\n```\n\n### Authentication Middleware\n\n```\nconst isAuthed = t.middleware(({ ctx, next }) => {\n  if (!ctx.session?.user) {\n    throw new TRPCError({ code: 'UNAUTHORIZED' });\n  }\n\n  return next({\n    ctx: {\n      ...ctx,\n      user: ctx.session.user, \u002F\u002F Narrow type\n    },\n  });\n});\n\n\u002F\u002F Protected procedure builder\nconst protectedProcedure = t.procedure.use(isAuthed);\n\nconst router = t.router({\n  \u002F\u002F Public\n  getPublicPosts: t.procedure.query(() => getPosts()),\n\n  \u002F\u002F Protected - requires authentication\n  getMyPosts: protectedProcedure.query(({ ctx }) => {\n    \u002F\u002F ctx.user is guaranteed to exist\n    return getPostsByUser(ctx.user.id);\n  }),\n});\n```\n\n### Chaining Middleware\n\n```\nconst timingMiddleware = t.middleware(async ({ next }) => {\n  const start = performance.now();\n  const result = await next();\n  console.log(`Execution time: ${performance.now() - start}ms`);\n  return result;\n});\n\nconst rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {\n  await checkRateLimit(ctx.session?.user?.id);\n  return next();\n});\n\nconst protectedProcedure = t.procedure\n  .use(timingMiddleware)\n  .use(rateLimitMiddleware)\n  .use(isAuthed);\n```\n\n### Context Transformation\n\n```\nconst enrichContextMiddleware = t.middleware(async ({ ctx, next }) => {\n  const user = ctx.session?.user\n    ? await ctx.db.user.findUnique({ where: { id: ctx.session.user.id } })\n    : null;\n\n  return next({\n    ctx: {\n      ...ctx,\n      user, \u002F\u002F Full user object\n    },\n  });\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Error Handling\n\n### TRPCError\n\n```\nimport { TRPCError } from '@trpc\u002Fserver';\n\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(async ({ input }) => {\n      const user = await db.user.findUnique({ where: { id: input } });\n\n      if (!user) {\n        throw new TRPCError({\n          code: 'NOT_FOUND',\n          message: `User ${input} not found`,\n        });\n      }\n\n      return user;\n    }),\n});\n```\n\n### Error Codes\n\n| Code | HTTP Status | Use Case |\n| --- | --- | --- |\n| `BAD_REQUEST` | 400 | Invalid input |\n| `UNAUTHORIZED` | 401 | Not authenticated |\n| `FORBIDDEN` | 403 | Not authorized |\n| `NOT_FOUND` | 404 | Resource not found |\n| `TIMEOUT` | 408 | Request timeout |\n| `CONFLICT` | 409 | Resource conflict |\n| `PRECONDITION_FAILED` | 412 | Precondition failed |\n| `PAYLOAD_TOO_LARGE` | 413 | Request too large |\n| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded |\n| `CLIENT_CLOSED_REQUEST` | 499 | Client closed connection |\n| `INTERNAL_SERVER_ERROR` | 500 | Server error |\n\n### Custom Error Handling\n\n```\nconst router = t.router({\n  deleteUser: t.procedure\n    .input(z.string())\n    .mutation(async ({ input, ctx }) => {\n      try {\n        return await ctx.db.user.delete({ where: { id: input } });\n      } catch (error) {\n        if (error.code === 'P2025') { \u002F\u002F Prisma not found\n          throw new TRPCError({\n            code: 'NOT_FOUND',\n            message: 'User not found',\n            cause: error,\n          });\n        }\n        throw new TRPCError({\n          code: 'INTERNAL_SERVER_ERROR',\n          message: 'Failed to delete user',\n          cause: error,\n        });\n      }\n    }),\n});\n```\n\n### Error Formatting\n\n```\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        zodError:\n          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError\n            ? error.cause.flatten()\n            : null,\n      },\n    };\n  },\n});\n```\n\n### Client-Side Error Handling\n\n```\nfunction MyComponent() {\n  const mutation = trpc.createUser.useMutation({\n    onError: (error) => {\n      if (error.data?.code === 'UNAUTHORIZED') {\n        router.push('\u002Flogin');\n      } else {\n        toast.error(error.message);\n      }\n    },\n  });\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Client Setup\n\n### Vanilla Client\n\n```\nimport { createTRPCProxyClient, httpBatchLink } from '@trpc\u002Fclient';\nimport type { AppRouter } from '.\u002Fserver';\n\nconst client = createTRPCProxyClient\u003CAppRouter>({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n    }),\n  ],\n});\n\n\u002F\u002F Usage\nconst user = await client.user.getById.query('123');\nconst newPost = await client.post.create.mutate({ title: 'Hello' });\n```\n\n### React Client Setup\n\n```\n\u002F\u002F utils\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '..\u002Fserver\u002Frouters';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n\n\u002F\u002F _app.tsx\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '..\u002Futils\u002Ftrpc';\n\nexport default function App({ Component, pageProps }: AppProps) {\n  const [queryClient] = useState(() => new QueryClient());\n  const [trpcClient] = useState(() =>\n    trpc.createClient({\n      links: [\n        httpBatchLink({\n          url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        \u003CComponent {...pageProps} \u002F>\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n### Next.js API Route\n\n```\n\u002F\u002F pages\u002Fapi\u002Ftrpc\u002F[trpc].ts\nimport { createNextApiHandler } from '@trpc\u002Fserver\u002Fadapters\u002Fnext';\nimport { appRouter } from '..\u002F..\u002F..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002F..\u002F..\u002Fserver\u002Fcontext';\n\nexport default createNextApiHandler({\n  router: appRouter,\n  createContext,\n});\n```\n\n### Headers & Authentication\n\n```\nconst trpcClient = trpc.createClient({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      headers: async () => {\n        const token = await getAuthToken();\n        return {\n          authorization: token ? `Bearer ${token}` : undefined,\n        };\n      },\n    }),\n  ],\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## React Query Integration\n\n### useQuery Hook\n\n```\nfunction UserProfile({ userId }: { userId: string }) {\n  const { data, isLoading, error } = trpc.user.getById.useQuery(userId);\n\n  if (isLoading) return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  if (error) return \u003Cdiv>Error: {error.message}\u003C\u002Fdiv>;\n\n  return \u003Cdiv>{data.name}\u003C\u002Fdiv>;\n}\n```\n\n### Query Options\n\n```\nconst { data } = trpc.posts.list.useQuery(undefined, {\n  refetchOnWindowFocus: false,\n  staleTime: 5 * 60 * 1000, \u002F\u002F 5 minutes\n  cacheTime: 10 * 60 * 1000, \u002F\u002F 10 minutes\n  retry: 3,\n  onSuccess: (data) => console.log('Fetched', data.length, 'posts'),\n});\n```\n\n### useMutation Hook\n\n```\nfunction CreatePostForm() {\n  const utils = trpc.useContext();\n\n  const createPost = trpc.post.create.useMutation({\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch\n      utils.post.list.invalidate();\n    },\n  });\n\n  const handleSubmit = (data: { title: string }) => {\n    createPost.mutate(data);\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" \u002F>\n      \u003Cbutton disabled={createPost.isLoading}>\n        {createPost.isLoading ? 'Creating...' : 'Create'}\n      \u003C\u002Fbutton>\n      {createPost.error && \u003Cp>{createPost.error.message}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Optimistic Updates\n\n```\nconst createPost = trpc.post.create.useMutation({\n  onMutate: async (newPost) => {\n    \u002F\u002F Cancel outgoing refetches\n    await utils.post.list.cancel();\n\n    \u002F\u002F Snapshot previous value\n    const previousPosts = utils.post.list.getData();\n\n    \u002F\u002F Optimistically update\n    utils.post.list.setData(undefined, (old) => [\n      ...(old ?? []),\n      { id: 'temp', ...newPost },\n    ]);\n\n    return { previousPosts };\n  },\n  onError: (err, newPost, context) => {\n    \u002F\u002F Rollback on error\n    utils.post.list.setData(undefined, context?.previousPosts);\n  },\n  onSettled: () => {\n    \u002F\u002F Refetch after success or error\n    utils.post.list.invalidate();\n  },\n});\n```\n\n### Infinite Queries\n\n```\n\u002F\u002F Server\nconst router = t.router({\n  posts: t.procedure\n    .input(z.object({\n      cursor: z.number().optional(),\n      limit: z.number().default(10),\n    }))\n    .query(({ input }) => {\n      const posts = getPosts(input.cursor, input.limit);\n      return {\n        posts,\n        nextCursor: posts.length === input.limit ? input.cursor + input.limit : undefined,\n      };\n    }),\n});\n\n\u002F\u002F Client\nfunction PostList() {\n  const {\n    data,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = trpc.posts.useInfiniteQuery(\n    { limit: 10 },\n    {\n      getNextPageParam: (lastPage) => lastPage.nextCursor,\n    }\n  );\n\n  return (\n    \u003Cdiv>\n      {data?.pages.map((page) =>\n        page.posts.map((post) => \u003CPostCard key={post.id} post={post} \u002F>)\n      )}\n      {hasNextPage && (\n        \u003Cbutton onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>\n          Load More\n        \u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Next.js App Router Integration\n\n### Server Components\n\n```\n\u002F\u002F app\u002Fusers\u002Fpage.tsx (Server Component)\nimport { createCaller } from '..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002Fserver\u002Fcontext';\n\nexport default async function UsersPage() {\n  const ctx = await createContext({ req: null, res: null });\n  const caller = createCaller(ctx);\n\n  const users = await caller.user.list();\n\n  return (\n    \u003Cdiv>\n      {users.map((user) => (\n        \u003Cdiv key={user.id}>{user.name}\u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Server Actions\n\n```\n\u002F\u002F app\u002Factions.ts\n'use server';\n\nimport { createCaller } from '..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002Fserver\u002Fcontext';\n\nexport async function createPost(formData: FormData) {\n  const ctx = await createContext({ req: null, res: null });\n  const caller = createCaller(ctx);\n\n  return caller.post.create({\n    title: formData.get('title') as string,\n    content: formData.get('content') as string,\n  });\n}\n```\n\n### App Router Provider\n\n```\n\u002F\u002F app\u002Fproviders.tsx\n'use client';\n\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '.\u002Ftrpc';\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  const [queryClient] = useState(() => new QueryClient());\n  const [trpcClient] = useState(() =>\n    trpc.createClient({\n      links: [\n        httpBatchLink({\n          url: '\u002Fapi\u002Ftrpc',\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        {children}\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n\n\u002F\u002F app\u002Flayout.tsx\nimport { Providers } from '.\u002Fproviders';\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        \u003CProviders>{children}\u003C\u002FProviders>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### Client Components in App Router\n\n```\n\u002F\u002F app\u002Fposts\u002Fcreate-button.tsx\n'use client';\n\nimport { trpc } from '..\u002Ftrpc';\n\nexport function CreatePostButton() {\n  const createPost = trpc.post.create.useMutation();\n\n  return (\n    \u003Cbutton onClick={() => createPost.mutate({ title: 'New Post' })}>\n      Create Post\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n### API Route Handler (App Router)\n\n```\n\u002F\u002F app\u002Fapi\u002Ftrpc\u002F[trpc]\u002Froute.ts\nimport { fetchRequestHandler } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { appRouter } from '..\u002F..\u002F..\u002F..\u002Fserver\u002Frouters';\nimport { createContext } from '..\u002F..\u002F..\u002F..\u002Fserver\u002Fcontext';\n\nconst handler = (req: Request) =>\n  fetchRequestHandler({\n    endpoint: '\u002Fapi\u002Ftrpc',\n    req,\n    router: appRouter,\n    createContext,\n  });\n\nexport { handler as GET, handler as POST };\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Real-time Subscriptions\n\n### WebSocket Setup (Server)\n\n```\nimport { applyWSSHandler } from '@trpc\u002Fserver\u002Fadapters\u002Fws';\nimport ws from 'ws';\n\nconst wss = new ws.Server({ port: 3001 });\n\napplyWSSHandler({\n  wss,\n  router: appRouter,\n  createContext,\n});\n\nconsole.log('WebSocket server listening on port 3001');\n```\n\n### Subscription Procedure\n\n```\nimport { observable } from '@trpc\u002Fserver\u002Fobservable';\nimport { EventEmitter } from 'events';\n\nconst ee = new EventEmitter();\n\nconst router = t.router({\n  onPostAdd: t.procedure.subscription(() => {\n    return observable\u003CPost>((emit) => {\n      const onAdd = (data: Post) => emit.next(data);\n\n      ee.on('add', onAdd);\n\n      return () => {\n        ee.off('add', onAdd);\n      };\n    });\n  }),\n\n  createPost: t.procedure\n    .input(z.object({ title: z.string() }))\n    .mutation(({ input }) => {\n      const post = { id: Date.now().toString(), ...input };\n      ee.emit('add', post); \u002F\u002F Emit to subscribers\n      return post;\n    }),\n});\n```\n\n### Client WebSocket Setup\n\n```\nimport { createWSClient, wsLink } from '@trpc\u002Fclient';\n\nconst wsClient = createWSClient({\n  url: 'ws:\u002F\u002Flocalhost:3001',\n});\n\nconst trpcClient = trpc.createClient({\n  links: [\n    wsLink({\n      client: wsClient,\n    }),\n  ],\n});\n```\n\n### useSubscription Hook\n\n```\nfunction PostFeed() {\n  const [posts, setPosts] = useState\u003CPost[]>([]);\n\n  trpc.onPostAdd.useSubscription(undefined, {\n    onData: (post) => {\n      setPosts((prev) => [post, ...prev]);\n    },\n    onError: (err) => {\n      console.error('Subscription error:', err);\n    },\n  });\n\n  return (\n    \u003Cdiv>\n      {posts.map((post) => (\n        \u003CPostCard key={post.id} post={post} \u002F>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Subscription with Input\n\n```\n\u002F\u002F Server\nconst router = t.router({\n  onUserStatusChange: t.procedure\n    .input(z.string())\n    .subscription(({ input }) => {\n      return observable\u003CUserStatus>((emit) => {\n        const onChange = (userId: string, status: UserStatus) => {\n          if (userId === input) {\n            emit.next(status);\n          }\n        };\n\n        ee.on('statusChange', onChange);\n        return () => ee.off('statusChange', onChange);\n      });\n    }),\n});\n\n\u002F\u002F Client\ntrpc.onUserStatusChange.useSubscription('user-123', {\n  onData: (status) => console.log('Status:', status),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## File Uploads\n\n### Multipart Form Data (Server)\n\n```\n\u002F\u002F Next.js API route with file upload\nimport { NextApiRequest, NextApiResponse } from 'next';\nimport formidable from 'formidable';\nimport fs from 'fs';\n\nexport const config = {\n  api: { bodyParser: false },\n};\n\nexport default async function handler(req: NextApiRequest, res: NextApiResponse) {\n  const form = formidable({ multiples: false });\n\n  form.parse(req, async (err, fields, files) => {\n    if (err) return res.status(500).json({ error: 'Upload failed' });\n\n    const file = files.file as formidable.File;\n    const buffer = fs.readFileSync(file.filepath);\n\n    \u002F\u002F Upload to S3, etc.\n    const url = await uploadToS3(buffer, file.originalFilename);\n\n    res.json({ url });\n  });\n}\n```\n\n### Base64 Upload (tRPC)\n\n```\n\u002F\u002F For small files only (\u003C1MB)\nconst router = t.router({\n  uploadAvatar: t.procedure\n    .input(z.object({\n      fileName: z.string(),\n      fileData: z.string(), \u002F\u002F Base64\n    }))\n    .mutation(async ({ input }) => {\n      const buffer = Buffer.from(input.fileData, 'base64');\n      const url = await uploadToS3(buffer, input.fileName);\n      return { url };\n    }),\n});\n\n\u002F\u002F Client\nconst uploadAvatar = trpc.uploadAvatar.useMutation();\n\nconst handleFileChange = (e: React.ChangeEvent\u003CHTMLInputElement>) => {\n  const file = e.target.files?.[0];\n  if (!file) return;\n\n  const reader = new FileReader();\n  reader.onload = () => {\n    const base64 = reader.result as string;\n    uploadAvatar.mutate({\n      fileName: file.name,\n      fileData: base64.split(',')[1], \u002F\u002F Remove data:image\u002F...;base64,\n    });\n  };\n  reader.readAsDataURL(file);\n};\n```\n\n### Signed URL Pattern (Recommended)\n\n```\n\u002F\u002F Step 1: Get signed upload URL from tRPC\nconst router = t.router({\n  getUploadUrl: t.procedure\n    .input(z.object({\n      fileName: z.string(),\n      fileType: z.string(),\n    }))\n    .mutation(async ({ input }) => {\n      const signedUrl = await s3.getSignedUrl('putObject', {\n        Bucket: 'my-bucket',\n        Key: input.fileName,\n        ContentType: input.fileType,\n        Expires: 60, \u002F\u002F 1 minute\n      });\n\n      return { uploadUrl: signedUrl, fileUrl: `https:\u002F\u002Fcdn.example.com\u002F${input.fileName}` };\n    }),\n});\n\n\u002F\u002F Step 2: Client uploads directly to S3\nasync function uploadFile(file: File) {\n  \u002F\u002F Get signed URL\n  const { uploadUrl, fileUrl } = await trpc.getUploadUrl.mutate({\n    fileName: file.name,\n    fileType: file.type,\n  });\n\n  \u002F\u002F Upload directly to S3\n  await fetch(uploadUrl, {\n    method: 'PUT',\n    body: file,\n    headers: { 'Content-Type': file.type },\n  });\n\n  \u002F\u002F Save file URL to database via tRPC\n  await trpc.user.updateAvatar.mutate({ url: fileUrl });\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Batch Requests & Data Loaders\n\n### Automatic Batching\n\n```\n\u002F\u002F Client configuration\nconst trpcClient = trpc.createClient({\n  links: [\n    httpBatchLink({\n      url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      maxBatchSize: 10, \u002F\u002F Batch up to 10 requests\n    }),\n  ],\n});\n\n\u002F\u002F Multiple calls made close together are batched into one HTTP request\nconst user1 = trpc.user.getById.useQuery('1');\nconst user2 = trpc.user.getById.useQuery('2');\nconst user3 = trpc.user.getById.useQuery('3');\n\u002F\u002F → Single HTTP request with 3 procedure calls\n```\n\n### DataLoader Pattern\n\n```\nimport DataLoader from 'dataloader';\n\n\u002F\u002F Create DataLoader in context\nexport async function createContext() {\n  const userLoader = new DataLoader(async (ids: readonly string[]) => {\n    const users = await db.user.findMany({\n      where: { id: { in: [...ids] } },\n    });\n\n    \u002F\u002F Return in same order as input\n    return ids.map((id) => users.find((u) => u.id === id));\n  });\n\n  return { userLoader };\n}\n\n\u002F\u002F Use in procedures\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(({ ctx, input }) => {\n      return ctx.userLoader.load(input); \u002F\u002F Batched!\n    }),\n\n  getPosts: t.procedure.query(async ({ ctx }) => {\n    const posts = await db.post.findMany({ take: 10 });\n\n    \u002F\u002F N+1 problem solved—all authors fetched in one query\n    const postsWithAuthors = await Promise.all(\n      posts.map(async (post) => ({\n        ...post,\n        author: await ctx.userLoader.load(post.authorId),\n      }))\n    );\n\n    return postsWithAuthors;\n  }),\n});\n```\n\n### Conditional Batching\n\n```\nimport { httpBatchLink, httpLink, splitLink } from '@trpc\u002Fclient';\n\nconst trpcClient = trpc.createClient({\n  links: [\n    splitLink({\n      \u002F\u002F Batch queries, don't batch mutations\n      condition: (op) => op.type === 'query',\n      true: httpBatchLink({ url: '\u002Fapi\u002Ftrpc' }),\n      false: httpLink({ url: '\u002Fapi\u002Ftrpc' }),\n    }),\n  ],\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## TypeScript Inference Patterns\n\n### Inferring Types from Router\n\n```\nimport type { inferRouterInputs, inferRouterOutputs } from '@trpc\u002Fserver';\nimport type { AppRouter } from '.\u002Fserver';\n\n\u002F\u002F Input types\ntype RouterInputs = inferRouterInputs\u003CAppRouter>;\ntype CreateUserInput = RouterInputs['user']['create'];\n\n\u002F\u002F Output types\ntype RouterOutputs = inferRouterOutputs\u003CAppRouter>;\ntype User = RouterOutputs['user']['getById'];\n\n\u002F\u002F Use in components\nfunction UserCard({ user }: { user: User }) {\n  return \u003Cdiv>{user.name}\u003C\u002Fdiv>;\n}\n```\n\n### Procedure Helpers\n\n```\nimport type { inferProcedureInput, inferProcedureOutput } from '@trpc\u002Fserver';\n\ntype CreatePostInput = inferProcedureInput\u003CAppRouter['post']['create']>;\ntype Post = inferProcedureOutput\u003CAppRouter['post']['getById']>;\n```\n\n### Context Type Inference\n\n```\nimport { inferAsyncReturnType } from '@trpc\u002Fserver';\n\nexport async function createContext() {\n  return {\n    db: prisma,\n    user: null as User | null,\n  };\n}\n\nexport type Context = inferAsyncReturnType\u003Ctypeof createContext>;\n\nconst t = initTRPC.context\u003CContext>().create();\n```\n\n### Generic Procedures\n\n```\n\u002F\u002F Reusable pagination\nfunction createPaginatedProcedure\u003CT>(\n  getData: (cursor: number, limit: number) => Promise\u003CT[]>\n) {\n  return t.procedure\n    .input(z.object({\n      cursor: z.number().optional(),\n      limit: z.number().default(10),\n    }))\n    .query(async ({ input }) => {\n      const items = await getData(input.cursor ?? 0, input.limit);\n      return {\n        items,\n        nextCursor: items.length === input.limit\n          ? (input.cursor ?? 0) + input.limit\n          : undefined,\n      };\n    });\n}\n\nconst router = t.router({\n  posts: createPaginatedProcedure((cursor, limit) =>\n    db.post.findMany({ skip: cursor, take: limit })\n  ),\n  users: createPaginatedProcedure((cursor, limit) =>\n    db.user.findMany({ skip: cursor, take: limit })\n  ),\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Testing Strategies\n\n### Unit Testing Procedures\n\n```\nimport { createCaller } from '..\u002Frouters';\n\ndescribe('User Router', () => {\n  it('should create user', async () => {\n    const ctx = {\n      db: mockDb,\n      session: null,\n    };\n\n    const caller = createCaller(ctx);\n\n    const result = await caller.user.create({\n      name: 'Alice',\n      email: 'alice@example.com',\n    });\n\n    expect(result).toMatchObject({\n      name: 'Alice',\n      email: 'alice@example.com',\n    });\n  });\n});\n```\n\n### Integration Testing\n\n```\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { createTRPCProxyClient } from '@trpc\u002Fclient';\nimport type { AppRouter } from '..\u002Fserver';\n\ndescribe('tRPC Integration', () => {\n  const client = createTRPCProxyClient\u003CAppRouter>({\n    links: [\n      httpBatchLink({\n        url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc',\n      }),\n    ],\n  });\n\n  it('should fetch user', async () => {\n    const user = await client.user.getById.query('123');\n    expect(user.id).toBe('123');\n  });\n});\n```\n\n### Mocking Context\n\n```\nimport { createCaller } from '..\u002Frouters';\n\nconst mockContext = {\n  db: {\n    user: {\n      findUnique: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),\n      create: vi.fn(),\n    },\n  },\n  session: {\n    user: { id: '1', email: 'alice@example.com' },\n  },\n};\n\nit('should get current user', async () => {\n  const caller = createCaller(mockContext);\n  const user = await caller.user.getMe();\n\n  expect(mockContext.db.user.findUnique).toHaveBeenCalledWith({\n    where: { id: '1' },\n  });\n  expect(user.name).toBe('Alice');\n});\n```\n\n### Testing React Hooks\n\n```\nimport { renderHook, waitFor } from '@testing-library\u002Freact';\nimport { createWrapper } from '.\u002Ftest-utils';\n\nit('should fetch posts', async () => {\n  const { result } = renderHook(() => trpc.post.list.useQuery(), {\n    wrapper: createWrapper(),\n  });\n\n  await waitFor(() => expect(result.current.isSuccess).toBe(true));\n\n  expect(result.current.data).toHaveLength(10);\n});\n\n\u002F\u002F test-utils.ts\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { trpc } from '..\u002Futils\u002Ftrpc';\n\nexport function createWrapper() {\n  const queryClient = new QueryClient();\n  const trpcClient = trpc.createClient({\n    links: [httpBatchLink({ url: 'http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc' })],\n  });\n\n  return ({ children }) => (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>\n        {children}\n      \u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Production Patterns\n\n### Error Monitoring\n\n```\nimport * as Sentry from '@sentry\u002Fnode';\n\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    \u002F\u002F Log to Sentry\n    if (error.code === 'INTERNAL_SERVER_ERROR') {\n      Sentry.captureException(error);\n    }\n\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        \u002F\u002F Don't expose internal errors in production\n        message: process.env.NODE_ENV === 'production' && error.code === 'INTERNAL_SERVER_ERROR'\n          ? 'Internal server error'\n          : shape.message,\n      },\n    };\n  },\n});\n```\n\n### Rate Limiting\n\n```\nimport { Ratelimit } from '@upstash\u002Fratelimit';\nimport { Redis } from '@upstash\u002Fredis';\n\nconst ratelimit = new Ratelimit({\n  redis: Redis.fromEnv(),\n  limiter: Ratelimit.slidingWindow(10, '10 s'),\n});\n\nconst rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {\n  const identifier = ctx.session?.user?.id ?? ctx.req.ip;\n  const { success } = await ratelimit.limit(identifier);\n\n  if (!success) {\n    throw new TRPCError({\n      code: 'TOO_MANY_REQUESTS',\n      message: 'Rate limit exceeded',\n    });\n  }\n\n  return next();\n});\n```\n\n### Caching\n\n```\nimport { Redis } from 'ioredis';\n\nconst redis = new Redis(process.env.REDIS_URL);\n\nconst router = t.router({\n  getUser: t.procedure\n    .input(z.string())\n    .query(async ({ input }) => {\n      \u002F\u002F Check cache\n      const cached = await redis.get(`user:${input}`);\n      if (cached) return JSON.parse(cached);\n\n      \u002F\u002F Fetch from database\n      const user = await db.user.findUnique({ where: { id: input } });\n\n      \u002F\u002F Cache for 5 minutes\n      await redis.setex(`user:${input}`, 300, JSON.stringify(user));\n\n      return user;\n    }),\n});\n```\n\n### Request Logging\n\n```\nconst loggingMiddleware = t.middleware(async ({ path, type, next, input }) => {\n  const start = Date.now();\n\n  console.log(`→ ${type} ${path}`, { input });\n\n  try {\n    const result = await next();\n    const duration = Date.now() - start;\n    console.log(`✓ ${type} ${path} - ${duration}ms`);\n    return result;\n  } catch (error) {\n    const duration = Date.now() - start;\n    console.error(`✗ ${type} ${path} - ${duration}ms`, { error });\n    throw error;\n  }\n});\n```\n\n### OpenTelemetry Integration\n\n```\nimport { trace } from '@opentelemetry\u002Fapi';\n\nconst tracingMiddleware = t.middleware(async ({ path, type, next }) => {\n  const tracer = trace.getTracer('trpc');\n\n  return tracer.startActiveSpan(`trpc.${type}.${path}`, async (span) => {\n    try {\n      const result = await next();\n      span.setStatus({ code: 0 }); \u002F\u002F OK\n      return result;\n    } catch (error) {\n      span.setStatus({ code: 2, message: error.message }); \u002F\u002F ERROR\n      span.recordException(error);\n      throw error;\n    } finally {\n      span.end();\n    }\n  });\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Comparison with REST & GraphQL\n\n### Feature Comparison\n\n| Feature | tRPC | REST | GraphQL |\n| --- | --- | --- | --- |\n| **Type Safety** | Full (TypeScript) | Manual\u002Fcodegen | Manual\u002Fcodegen |\n| **Code Generation** | None | Optional (OpenAPI) | Required |\n| **Learning Curve** | Low | Low | Medium\u002FHigh |\n| **Client Libraries** | TypeScript only | Any language | Any language |\n| **API Documentation** | TypeScript types | OpenAPI\u002FSwagger | Schema\u002Fintrospection |\n| **Public APIs** | ❌ No | ✅ Yes | ✅ Yes |\n| **Flexible Queries** | ❌ Fixed | ❌ Fixed | ✅ Yes |\n| **Overfetching** | Minimal | Common | None |\n| **Caching** | React Query | HTTP caching | Complex |\n| **Real-time** | WebSocket | SSE\u002FWebSocket | Subscriptions |\n| **File Uploads** | Workarounds | Native | Complex |\n\n### When to Choose Each\n\n**tRPC**:\n\n- ✅ Full-stack TypeScript monorepo\n- ✅ Internal tools and dashboards\n- ✅ Next.js applications\n- ✅ Rapid development with small teams\n- ❌ Public APIs for external consumers\n- ❌ Multi-language clients\n\n**REST**:\n\n- ✅ Public APIs with broad compatibility\n- ✅ Multi-language services\n- ✅ HTTP caching requirements\n- ✅ File uploads and downloads\n- ❌ Complex nested data structures\n- ❌ Need for type safety without codegen\n\n**GraphQL**:\n\n- ✅ Complex data graphs\n- ✅ Multiple client types (web, mobile, etc.)\n- ✅ Need for flexible queries\n- ✅ Avoiding overfetching\n- ❌ Simple CRUD operations\n- ❌ Small teams (complexity overhead)\n\n### Migration Path\n\ntRPC can **coexist** with REST\u002FGraphQL:\n\n```\n\u002F\u002F Use tRPC for internal, REST for public\nconst router = t.router({\n  internal: internalRouter, \u002F\u002F tRPC only\n});\n\n\u002F\u002F Expose REST endpoints separately\napp.get('\u002Fapi\u002Fpublic\u002Fusers', publicRestHandler);\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Migration from REST\n\n### Gradual Migration Strategy\n\n1. **Add tRPC alongside REST**: Don't rewrite everything at once\n2. **New features in tRPC**: Start with new endpoints\n3. **Migrate high-value endpoints**: Focus on complex or frequently used APIs\n4. **Keep public APIs in REST**: Only migrate internal consumption\n\n### Converting REST to tRPC\n\n**Before (REST)**:\n\n```\n\u002F\u002F pages\u002Fapi\u002Fusers\u002F[id].ts\nexport default async function handler(req, res) {\n  if (req.method === 'GET') {\n    const user = await db.user.findUnique({ where: { id: req.query.id } });\n    res.json(user);\n  } else if (req.method === 'PATCH') {\n    const user = await db.user.update({\n      where: { id: req.query.id },\n      data: req.body,\n    });\n    res.json(user);\n  }\n}\n\n\u002F\u002F Client\nconst response = await fetch(`\u002Fapi\u002Fusers\u002F${id}`);\nconst user = await response.json(); \u002F\u002F No types!\n```\n\n**After (tRPC)**:\n\n```\n\u002F\u002F server\u002Frouters\u002Fuser.ts\nexport const userRouter = t.router({\n  getById: t.procedure\n    .input(z.string())\n    .query(({ input }) => db.user.findUnique({ where: { id: input } })),\n\n  update: t.procedure\n    .input(z.object({\n      id: z.string(),\n      data: z.object({ name: z.string().optional() }),\n    }))\n    .mutation(({ input }) => db.user.update({\n      where: { id: input.id },\n      data: input.data,\n    })),\n});\n\n\u002F\u002F Client\nconst user = await trpc.user.getById.query(id); \u002F\u002F Fully typed!\n```\n\n### Shared Validation\n\n```\n\u002F\u002F Reuse Zod schemas across REST and tRPC during migration\nimport { createUserSchema } from '..\u002Fschemas\u002Fuser';\n\n\u002F\u002F tRPC\nconst router = t.router({\n  createUser: t.procedure\n    .input(createUserSchema)\n    .mutation(({ input }) => createUser(input)),\n});\n\n\u002F\u002F REST (validate with same schema)\nexport default async function handler(req, res) {\n  const parsed = createUserSchema.safeParse(req.body);\n  if (!parsed.success) {\n    return res.status(400).json({ errors: parsed.error });\n  }\n  const user = await createUser(parsed.data);\n  res.json(user);\n}\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Best Practices & Performance\n\n### Code Organization\n\n```\nserver\u002F\n├── trpc.ts              # tRPC instance, base procedures\n├── context.ts           # Context creation\n├── middleware\u002F\n│   ├── auth.ts          # Authentication middleware\n│   ├── logging.ts       # Logging middleware\n│   └── rateLimit.ts     # Rate limiting\n├── routers\u002F\n│   ├── _app.ts          # Root router\n│   ├── user.ts          # User procedures\n│   ├── post.ts          # Post procedures\n│   └── admin\u002F\n│       └── index.ts     # Admin-only procedures\n└── schemas\u002F\n    ├── user.ts          # User Zod schemas\n    └── post.ts          # Post Zod schemas\n```\n\n### Performance Tips\n\n1. **Use batching for multiple queries**:\n\n   ```\n   httpBatchLink({ url: '\u002Fapi\u002Ftrpc', maxBatchSize: 10 })\n   ```\n2. **Implement DataLoader for N+1 queries**:\n\n   ```\n   const userLoader = new DataLoader(batchLoadUsers);\n   ```\n3. **Cache expensive queries**:\n\n   ```\n   trpc.posts.list.useQuery(undefined, { staleTime: 5 * 60 * 1000 });\n   ```\n4. **Optimize database queries**:\n\n   ```\n   \u002F\u002F ❌ Bad: N+1 query\n   const posts = await db.post.findMany();\n   const postsWithAuthors = await Promise.all(\n     posts.map((p) => db.user.findUnique({ where: { id: p.authorId } }))\n   );\n\n   \u002F\u002F ✅ Good: Single query with include\n   const posts = await db.post.findMany({\n     include: { author: true },\n   });\n   ```\n5. **Use React Query's deduplication**:\n\n   ```\n   \u002F\u002F Multiple components can call same query—React Query deduplicates\n   const { data } = trpc.user.getMe.useQuery();\n   ```\n\n### Security Best Practices\n\n1. **Always validate input with Zod**\n2. **Use middleware for authentication**:\n\n   ```\n   const protectedProcedure = t.procedure.use(isAuthed);\n   ```\n3. **Sanitize error messages in production**\n4. **Implement rate limiting**\n5. **Use HTTPS in production**\n6. **Set CORS properly**:\n\n   ```\n   createNextApiHandler({\n     router: appRouter,\n     createContext,\n     onError: ({ error }) => {\n       if (error.code === 'INTERNAL_SERVER_ERROR') {\n         console.error('Internal error:', error);\n       }\n     },\n   });\n   ```\n\n### Type Safety Tips\n\n1. **Export router type, not implementation**:\n\n   ```\n   export type AppRouter = typeof appRouter; \u002F\u002F ✅\n   \u002F\u002F Don't export `appRouter` itself to client\n   ```\n2. **Use `satisfies` for better inference**:\n\n   ```\n   const input = {\n     name: 'Alice',\n     age: 30,\n   } satisfies CreateUserInput;\n   ```\n3. **Avoid `any` in context**:\n\n   ```\n   \u002F\u002F ❌ Bad\n   ctx: { user: any }\n\n   \u002F\u002F ✅ Good\n   ctx: { user: User | null }\n   ```\n\n### Development Workflow\n\n1. **Define schema first**: Write Zod schemas before procedures\n2. **Test procedures in isolation**: Use `createCaller` for unit tests\n3. **Use TypeScript strict mode**: Catch type errors early\n4. **Enable React Query DevTools**:\n\n   ```\n   import { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools';\n\n   \u003CReactQueryDevtools initialIsOpen={false} \u002F>\n   ```\n\n### Common Pitfalls\n\n❌ **Don't return sensitive data**:\n\n```\n\u002F\u002F Bad: Exposes password hash\n.query(() => db.user.findMany())\n\n\u002F\u002F Good: Select specific fields\n.query(() => db.user.findMany({ select: { id: true, name: true } }))\n```\n\n❌ **Don't use mutations for reads**:\n\n```\n\u002F\u002F Bad: Side-effect-free operation as mutation\ngetMostRecentPost: t.procedure.mutation(() => getPost())\n\n\u002F\u002F Good: Use query for reads\ngetMostRecentPost: t.procedure.query(() => getPost())\n```\n\n❌ **Don't skip input validation**:\n\n```\n\u002F\u002F Bad: No validation\n.input(z.any())\n\n\u002F\u002F Good: Strict validation\n.input(z.object({ id: z.string().uuid() }))\n```\n\n### Monitoring & Observability\n\n```\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    \u002F\u002F Log metrics\n    metrics.increment('trpc.error', { code: error.code });\n\n    \u002F\u002F Send to error tracking\n    if (error.code === 'INTERNAL_SERVER_ERROR') {\n      Sentry.captureException(error);\n    }\n\n    return shape;\n  },\n});\n\nconst loggingMiddleware = t.middleware(async ({ path, type, next }) => {\n  const start = Date.now();\n  const result = await next();\n\n  \u002F\u002F Log performance metrics\n  metrics.timing('trpc.duration', Date.now() - start, { path, type });\n\n  return result;\n});\n```\n\n---\ndescription: Builds end-to-end type-safe tRPC routers with Zod input validation.\n\n## Summary\n\n**tRPC** enables type-safe APIs with minimal boilerplate:\n\n- ✅ **No code generation**: Types inferred from TypeScript\n- ✅ **React Query integration**: Built-in caching and optimistic updates\n- ✅ **Next.js first-class support**: App Router, Server Components\n- ✅ **Developer experience**: Auto-complete, refactoring, type errors\n\n**Best for**: Full-stack TypeScript apps, Next.js projects, internal tools\n**Avoid for**: Public APIs, multi-language services\n\n**Get Started**: Install → Define router → Use in client → Enjoy type safety!\n\n**Related Skills**: Zod (validation), React Query (caching), Next.js (integration)\n\n",{"github":43,"source":44,"install":45,"installs":6,"scraped_at":46,"description":7,"allowed-tools":47},"https:\u002F\u002Fgithub.com\u002Fbobmatnyc\u002Fclaude-mpm-skills","https:\u002F\u002Fskills.sh\u002Fbobmatnyc\u002Fclaude-mpm-skills\u002Ftrpc-type-safety","npx skills add bobmatnyc\u002Fclaude-mpm-skills --skill trpc-type-safety",1775315278000,[48,49],"read_file","list_files","main","a6e00c7fdd57f28e26658814f41ae0da22979aac",".agents\u002Fskills\u002Fclaude-mpm-skills\u002Ftrpc-type-safety",0,"https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council","https:\u002F\u002Fgithub.com\u002FJackSmack1971","2026-04-05 23:04:47",{"source":8,"skillName":5,"commands":58,"prompt":67,"zipAvailable":68},[59,61,64],{"tool":23,"command":60},"npx skills add JackSmack1971\u002Ffullstack-council --skill trpc-type-safety",{"tool":62,"command":63},"bunx","bunx skills add JackSmack1971\u002Ffullstack-council --skill trpc-type-safety",{"tool":65,"command":66},"pnpm","pnpm dlx skills add JackSmack1971\u002Ffullstack-council --skill trpc-type-safety","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fclaude-mpm-skills\u002Ftrpc-type-safety\u002FSKILL.md",true,[],[71,101,118,135,159,175],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":82,"publishedAt":38},"01M6KS7VQCYXBYGK8YGJWP4WV3","wes-bos-fullstack-educator","Activates a Wes Bos-style hands-on full-stack JavaScript educator persona that ships production-ready code with live-workshop energy. Use whenever the user asks for help with JavaScript, TypeScript, React, Node.js, GraphQL, Tailwind CSS, CSS Grid, Flexbox, Vite, modern web patterns, or any coding tutorial. Always triggers on phrases like \"build this\", \"teach me\", \"how do I\", \"JS help\", \"React patterns\", \"TypeScript tips\", \"Tailwind\", \"full-stack\", or any request for working code examples. Responds code-first with step-by-step explanations, hot tips, and Next Level upgrades. Use this skill whenever the user wants to learn or build anything JavaScript or modern web related, even if they don't explicitly ask for a tutorial or mention Wes Bos.","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fwes-bos-fullstack-educator\u002FSKILL.md",[77],{"categoryNo":78,"categoryName":79,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[83,84,85,86,87,88,89,90,91,92,93,94,33,95,31,96,97,98,99,100],"fullstack","wes bos fullstack educator","css","react","learn","code","Tailwind","how","persona","stack","node","ask","ready","me","next","production-ready","request","energy",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":38},"01M6KS7PM7W2S1739EZS18T0HA","ai-sdk","Builds AI-powered applications using the Vercel AI SDK with streaming and tool use.","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvercel\u002Fai-sdk\u002FSKILL.md",[],[108,109,37,110,111,112,36,35,113,114,33,32,115,116,31,117,23,27,24],"sdk","ai sdk","add vercel","streaming","tool use","applications","tool","use","vercel","vercel ai sdk",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":124,"publishedAt":38},"01M6KS7FYMD04AWFNNS149944S","next-best-practices","Enforces Next.js App Router best practices, performance, and deployment patterns.","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvercel-labs\u002Fnext-best-practices\u002FSKILL.md",[],[125,126,97,37,127,110,128,35,129,33,32,116,130,31,131,23,132,133,134,27],"best practices","next best practices","performance","deployment","Next.js","router","best-practices","patterns","deployment patterns","app",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":38},"01M6KS6Y6XXD42YM2T1GVHP3B0","troy-hunt-security","Activates the Troy Hunt persona for information security, threat modeling, and application hardening. Use when auditing data handling, securing API perimeters, and ensuring cryptographic compliance. Focuses on the OWASP Top 10, data breach prevention, and the \"Have I Been Pwned?\" principles.","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Ftroy-hunt-security\u002FSKILL.md",[],[142,143,144,37,145,146,147,148,91,115,149,150,151,152,153,154,155,156,157,158],"security","hunt","troy hunt security","owasp top 10","sentry","supabase","api","schema","only","get","principles","information","read-only","errors","top","for","breach",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":165,"publishedAt":38},"01M6KS6S94M1R78A6NBMYP3PE1","theo-browne-fullstack-advisor","Activates the Theo Browne persona — T3 Stack creator, ex-Twitch engineer, CEO of T3 Chat \u002F Ping.gg (YC W22), TypeScript absolutist, and opinionated full-stack advisor. Use this skill whenever the user asks about Next.js App Router, tRPC, Prisma, Zod, Tailwind, create-t3-app, T3 Stack architecture, end-to-end type safety, TypeScript tooling, rapid prototyping, DX critique, over-engineering problems, cargo-culting, full-stack TypeScript decisions, or wants direct hot-take feedback on tech choices and patterns. Also triggers on \"what would Theo think\", \"T3 style\", \"roast my stack\", or \"is this over-engineered\".","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Ftheo-browne-fullstack-advisor\u002FSKILL.md",[],[166,83,167,168,169,89,170,91,92,34,171,130,172,31,97,173,29,134,174,21],"advisor","theo browne fullstack advisor","architecture","create","critique","command","engineer","prisma","prototyping",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":181,"publishedAt":38},"01M6KS6Q5B0G0RYC3XJG1F98KC","stripe-best-practices","TBD - Please provide a keyword-dense capability description.","https:\u002F\u002Fgithub.com\u002FJackSmack1971\u002Ffullstack-council\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fstripe\u002Fstripe-best-practices\u002FSKILL.md",[],[125,182,183,35,33,32,31,184],"stripe best practices","stripe","please"]