[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7CEKEGG2Q04DRDPRSSM4APF":3,"skill-same:01M7CEKEGG2Q04DRDPRSSM4APF":64},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":41,"commitSha":42,"skillDirectory":43,"forksCount":44,"repositoryHtmlUrl":45,"repositoryHomepageUrl":46,"ownerHtmlUrl":47,"ownerBlogUrl":6,"ownerDescription":48,"updatedAt":49,"install":50,"resources":63},"01M7CEKEGG2Q04DRDPRSSM4APF","trpc-fullstack",null,"Build end-to-end type-safe APIs with tRPC — routers, procedures, middleware, subscriptions, and Next.js\u002FReact integration patterns.","YYC-Cube\u002FYYC3-Knowledge-Base","YYC-Cube","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235006543?v=4","01M79J2PH4SWD2X0825X6PN748","yyc3","01M79J2PHVK3F6ZBYT8K2SCZCW",1,"MIT","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftrpc-fullstack\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: trpc-fullstack\ndescription: \"Build end-to-end type-safe APIs with tRPC — routers, procedures, middleware, subscriptions, and Next.js\u002FReact integration patterns.\"\ncategory: framework\nrisk: none\nsource: community\ndate_added: \"2026-03-17\"\nauthor: suhaibjanjua\ntags: [typescript, trpc, api, fullstack, nextjs, react, type-safety]\ntools: [claude, cursor, gemini]\n---\n\n# tRPC Full-Stack\n\n## Overview\n\ntRPC lets you build fully type-safe APIs without writing a schema or code-generation step. Your TypeScript types flow from the server router directly to the client — so every API call is autocompleted, validated at compile time, and refactoring-safe. Use this skill when building TypeScript monorepos, Next.js apps, or any project where the server and client share a codebase.\n\n## When to Use This Skill\n\n- Use when building a TypeScript full-stack app (Next.js, Remix, Express + React) where the client and server share a single repo\n- Use when you want end-to-end type safety on API calls without REST\u002FGraphQL schema overhead\n- Use when adding real-time features (subscriptions) to an existing tRPC setup\n- Use when designing multi-step middleware (auth, rate limiting, tenant scoping) on tRPC procedures\n- Use when migrating an existing REST\u002FGraphQL API to tRPC incrementally\n\n## Core Concepts\n\n### Routers and Procedures\n\nA **router** groups related **procedures** (think: endpoints). Procedures are typed functions — `query` for reads, `mutation` for writes, `subscription` for real-time streams.\n\n### Input Validation with Zod\n\nAll procedure inputs are validated with Zod schemas. The validated, typed input is available in the procedure handler — no manual parsing.\n\n### Context\n\n`context` is shared state passed to every procedure — auth session, database client, request headers, etc. It is built once per request in a context factory. **Important:** Next.js App Router and Pages Router require separate context factories because App Router handlers receive a fetch `Request`, not a Node.js `NextApiRequest`.\n\n### Middleware\n\nMiddleware chains run before a procedure. Use them for authentication, logging, and request enrichment. They can extend the context for downstream procedures.\n\n---\n\n## How It Works\n\n### Step 1: Install and Initialize\n\n```bash\nnpm install @trpc\u002Fserver @trpc\u002Fclient @trpc\u002Freact-query @tanstack\u002Freact-query zod\n```\n\nCreate the tRPC instance and reusable builders:\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Ftrpc.ts\nimport { initTRPC, TRPCError } from '@trpc\u002Fserver';\nimport { type Context } from '.\u002Fcontext';\nimport { ZodError } from 'zod';\n\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        zodError:\n          error.cause instanceof ZodError ? error.cause.flatten() : null,\n      },\n    };\n  },\n});\n\nexport const router = t.router;\nexport const publicProcedure = t.procedure;\nexport const middleware = t.middleware;\n```\n\n### Step 2: Define Two Context Factories\n\nNext.js App Router handlers receive a fetch `Request` (not a Node.js `NextApiRequest`), so the context\nmust be built differently depending on the call site. Define one factory per surface:\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Fcontext.ts\nimport { type FetchCreateContextFnOptions } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { auth } from '@\u002Fserver\u002Fauth'; \u002F\u002F Next-Auth v5 \u002F your auth helper\nimport { db } from '.\u002Fdb';\n\n\u002F**\n * Context for the HTTP handler (App Router Route Handler).\n * `opts.req` is the fetch Request — auth is resolved server-side via `auth()`.\n *\u002F\nexport async function createTRPCContext(opts: FetchCreateContextFnOptions) {\n  const session = await auth(); \u002F\u002F server-side auth — no req\u002Fres needed\n  return { session, db, headers: opts.req.headers };\n}\n\n\u002F**\n * Context for direct server-side callers (Server Components, RSC, cron jobs).\n * No HTTP request is involved, so we call auth() directly from the server.\n *\u002F\nexport async function createServerContext() {\n  const session = await auth();\n  return { session, db };\n}\n\nexport type Context = Awaited\u003CReturnType\u003Ctypeof createTRPCContext>>;\n```\n\n### Step 3: Build an Auth Middleware and Protected Procedure\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Ftrpc.ts (continued)\nconst enforceAuth = middleware(({ ctx, next }) => {\n  if (!ctx.session?.user) {\n    throw new TRPCError({ code: 'UNAUTHORIZED' });\n  }\n  return next({\n    ctx: {\n      \u002F\u002F Narrows type: session is non-null from here\n      session: { ...ctx.session, user: ctx.session.user },\n    },\n  });\n});\n\nexport const protectedProcedure = t.procedure.use(enforceAuth);\n```\n\n### Step 4: Create Routers\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Frouters\u002Fpost.ts\nimport { z } from 'zod';\nimport { router, publicProcedure, protectedProcedure } from '..\u002Ftrpc';\nimport { TRPCError } from '@trpc\u002Fserver';\n\nexport const postRouter = router({\n  list: publicProcedure\n    .input(\n      z.object({\n        limit: z.number().min(1).max(100).default(20),\n        cursor: z.string().optional(),\n      })\n    )\n    .query(async ({ ctx, input }) => {\n      const posts = await ctx.db.post.findMany({\n        take: input.limit + 1,\n        cursor: input.cursor ? { id: input.cursor } : undefined,\n        orderBy: { createdAt: 'desc' },\n      });\n      const nextCursor =\n        posts.length > input.limit ? posts.pop()!.id : undefined;\n      return { posts, nextCursor };\n    }),\n\n  byId: publicProcedure\n    .input(z.object({ id: z.string() }))\n    .query(async ({ ctx, input }) => {\n      const post = await ctx.db.post.findUnique({ where: { id: input.id } });\n      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });\n      return post;\n    }),\n\n  create: protectedProcedure\n    .input(\n      z.object({\n        title: z.string().min(1).max(200),\n        body: z.string().min(1),\n      })\n    )\n    .mutation(async ({ ctx, input }) => {\n      return ctx.db.post.create({\n        data: { ...input, authorId: ctx.session.user.id },\n      });\n    }),\n\n  delete: protectedProcedure\n    .input(z.object({ id: z.string() }))\n    .mutation(async ({ ctx, input }) => {\n      const post = await ctx.db.post.findUnique({ where: { id: input.id } });\n      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });\n      if (post.authorId !== ctx.session.user.id)\n        throw new TRPCError({ code: 'FORBIDDEN' });\n      return ctx.db.post.delete({ where: { id: input.id } });\n    }),\n});\n```\n\n### Step 5: Compose the Root Router and Export Types\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Froot.ts\nimport { router } from '.\u002Ftrpc';\nimport { postRouter } from '.\u002Frouters\u002Fpost';\nimport { userRouter } from '.\u002Frouters\u002Fuser';\n\nexport const appRouter = router({\n  post: postRouter,\n  user: userRouter,\n});\n\n\u002F\u002F Export the type for the client — never import the appRouter itself on the client\nexport type AppRouter = typeof appRouter;\n```\n\n### Step 6: Mount the API Handler (Next.js App Router)\n\nThe App Router handler must use `fetchRequestHandler` and the **fetch-based** context factory.\n`createTRPCContext` receives `FetchCreateContextFnOptions` (with a fetch `Request`), not\na Pages Router `req\u002Fres` pair.\n\n```typescript\n\u002F\u002F src\u002Fapp\u002Fapi\u002Ftrpc\u002F[trpc]\u002Froute.ts\nimport { fetchRequestHandler } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { type FetchCreateContextFnOptions } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { appRouter } from '@\u002Fserver\u002Froot';\nimport { createTRPCContext } from '@\u002Fserver\u002Fcontext';\n\nconst handler = (req: Request) =>\n  fetchRequestHandler({\n    endpoint: '\u002Fapi\u002Ftrpc',\n    req,\n    router: appRouter,\n    \u002F\u002F opts is FetchCreateContextFnOptions — req is the fetch Request\n    createContext: (opts: FetchCreateContextFnOptions) => createTRPCContext(opts),\n  });\n\nexport { handler as GET, handler as POST };\n```\n\n### Step 7: Set Up the Client (React Query)\n\n```typescript\n\u002F\u002F src\u002Futils\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '@\u002Fserver\u002Froot';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n```\n\n```typescript\n\u002F\u002F src\u002Fapp\u002Fproviders.tsx\n'use client';\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function TRPCProvider({ 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          headers: () => ({ 'x-trpc-source': 'react' }),\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>{children}\u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n---\n\n## Examples\n\n### Example 1: Fetching Data in a Component\n\n```typescript\n\u002F\u002F components\u002FPostList.tsx\n'use client';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function PostList() {\n  const { data, isLoading, error } = trpc.post.list.useQuery({ limit: 10 });\n\n  if (isLoading) return \u003Cp>Loading…\u003C\u002Fp>;\n  if (error) return \u003Cp>Error: {error.message}\u003C\u002Fp>;\n\n  return (\n    \u003Cul>\n      {data?.posts.map((post) => (\n        \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Example 2: Mutation with Cache Invalidation\n\n```typescript\n'use client';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function CreatePost() {\n  const utils = trpc.useUtils();\n\n  const createPost = trpc.post.create.useMutation({\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch the post list\n      utils.post.list.invalidate();\n    },\n  });\n\n  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n    e.preventDefault();\n    const form = e.currentTarget;\n    const data = new FormData(form);\n    createPost.mutate({\n      title: data.get('title') as string,\n      body: data.get('body') as string,\n    });\n    form.reset();\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" placeholder=\"Title\" required \u002F>\n      \u003Ctextarea name=\"body\" placeholder=\"Body\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={createPost.isPending}>\n        {createPost.isPending ? 'Creating…' : 'Create Post'}\n      \u003C\u002Fbutton>\n      {createPost.error && \u003Cp>{createPost.error.message}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Example 3: Server-Side Caller (Server Components \u002F SSR)\n\nUse `createServerContext` — the dedicated server-side factory — so that `auth()` is called\ncorrectly without needing a synthetic or empty request object:\n\n```typescript\n\u002F\u002F app\u002Fposts\u002Fpage.tsx (Next.js Server Component)\nimport { appRouter } from '@\u002Fserver\u002Froot';\nimport { createCallerFactory } from '@trpc\u002Fserver';\nimport { createServerContext } from '@\u002Fserver\u002Fcontext';\n\nconst createCaller = createCallerFactory(appRouter);\n\nexport default async function PostsPage() {\n  \u002F\u002F Uses createServerContext — calls auth() server-side, no req\u002Fres cast needed\n  const caller = createCaller(await createServerContext());\n  const { posts } = await caller.post.list({ limit: 20 });\n\n  return (\n    \u003Cul>\n      {posts.map((post) => (\n        \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Example 4: Real-Time Subscriptions (WebSocket)\n\n```typescript\n\u002F\u002F server\u002Frouters\u002Fnotifications.ts\nimport { observable } from '@trpc\u002Fserver\u002Fobservable';\nimport { EventEmitter } from 'events';\n\nconst ee = new EventEmitter();\n\nexport const notificationRouter = router({\n  onNew: protectedProcedure.subscription(({ ctx }) => {\n    return observable\u003C{ message: string; at: Date }>((emit) => {\n      const onNotification = (data: { message: string }) => {\n        emit.next({ message: data.message, at: new Date() });\n      };\n\n      const channel = `user:${ctx.session.user.id}`;\n      ee.on(channel, onNotification);\n      return () => ee.off(channel, onNotification);\n    });\n  }),\n});\n```\n\n```typescript\n\u002F\u002F Client usage — requires wsLink in the client config\ntrpc.notification.onNew.useSubscription(undefined, {\n  onData(data) {\n    toast(data.message);\n  },\n});\n```\n\n---\n\n## Best Practices\n\n- ✅ **Export only `AppRouter` type** from server code — never import `appRouter` on the client\n- ✅ **Use separate context factories** — `createTRPCContext` for the HTTP handler, `createServerContext` for Server Components and callers\n- ✅ **Validate all inputs with Zod** — never trust raw `input` without a schema\n- ✅ **Split routers by domain** (posts, users, billing) and merge in `root.ts`\n- ✅ **Extend context in middleware** rather than querying the DB multiple times per request\n- ✅ **Use `utils.invalidate()`** after mutations to keep the cache fresh\n- ❌ **Don't cast context with `as any`** to silence type errors — the mismatch will surface as a runtime failure when auth or session lookups return undefined\n- ❌ **Don't use `createContext({} as any)`** in Server Components — use `createServerContext()` which calls `auth()` directly\n- ❌ **Don't put business logic in the route handler** — keep it in the procedure or a service layer\n- ❌ **Don't share the tRPC client instance globally** — create it per-provider to avoid stale closures\n\n---\n\n## Security & Safety Notes\n\n- Always enforce authorization in `protectedProcedure` — never rely on client-side checks alone\n- Validate all input shapes with Zod, including pagination cursors and IDs, to prevent injection via malformed inputs\n- Avoid exposing internal error details to clients — use `TRPCError` with a public-safe `message` and keep stack traces server-side only\n- Rate-limit public procedures using middleware to prevent abuse\n\n---\n\n## Common Pitfalls\n\n- **Problem:** Auth session is `null` in protected procedures even when the user is logged in\n  **Solution:** Ensure `createTRPCContext` uses the correct server-side auth call (e.g. `auth()` from Next-Auth v5) and is not receiving a Pages Router `req\u002Fres` cast via `as any` in an App Router handler\n\n- **Problem:** Server Component caller fails for auth-dependent queries\n  **Solution:** Use `createServerContext()` (the dedicated server-side factory) instead of passing an empty or synthetic object to `createContext`\n\n- **Problem:** \"Type error: AppRouter is not assignable to AnyRouter\"\n  **Solution:** Import `AppRouter` as a `type` import (`import type { AppRouter }`) on the client, not the full module\n\n- **Problem:** Mutations not reflecting in the UI after success\n  **Solution:** Call `utils.\u003Crouter>.\u003Cprocedure>.invalidate()` in `onSuccess` to trigger a refetch via React Query\n\n- **Problem:** \"Cannot find module '@trpc\u002Fserver\u002Fadapters\u002Fnext'\" with App Router\n  **Solution:** Use `@trpc\u002Fserver\u002Fadapters\u002Ffetch` and `fetchRequestHandler` for the App Router; the `nextjs` adapter is for Pages Router only\n\n- **Problem:** Subscriptions not connecting\n  **Solution:** Subscriptions require `splitLink` — route subscriptions to `wsLink` and queries\u002Fmutations to `httpBatchLink`\n\n---\n\n## Related Skills\n\n- `@typescript-expert` — Deep TypeScript patterns used inside tRPC routers and generic utilities\n- `@react-patterns` — React hooks patterns that pair with `trpc.*.useQuery` and `useMutation`\n- `@test-driven-development` — Write procedure unit tests using `createCallerFactory` without an HTTP server\n- `@security-auditor` — Review tRPC middleware chains for auth bypass and input validation gaps\n\n## Additional Resources\n\n- [tRPC Official Docs](https:\u002F\u002Ftrpc.io\u002Fdocs)\n- [create-t3-app](https:\u002F\u002Fcreate.t3.gg) — Production Next.js starter with tRPC wired in\n- [tRPC GitHub](https:\u002F\u002Fgithub.com\u002Ftrpc\u002Ftrpc)\n- [TanStack Query Docs](https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest)\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","# tRPC Full-Stack\n\n## Overview\n\ntRPC lets you build fully type-safe APIs without writing a schema or code-generation step. Your TypeScript types flow from the server router directly to the client — so every API call is autocompleted, validated at compile time, and refactoring-safe. Use this skill when building TypeScript monorepos, Next.js apps, or any project where the server and client share a codebase.\n\n## When to Use This Skill\n\n- Use when building a TypeScript full-stack app (Next.js, Remix, Express + React) where the client and server share a single repo\n- Use when you want end-to-end type safety on API calls without REST\u002FGraphQL schema overhead\n- Use when adding real-time features (subscriptions) to an existing tRPC setup\n- Use when designing multi-step middleware (auth, rate limiting, tenant scoping) on tRPC procedures\n- Use when migrating an existing REST\u002FGraphQL API to tRPC incrementally\n\n## Core Concepts\n\n### Routers and Procedures\n\nA **router** groups related **procedures** (think: endpoints). Procedures are typed functions — `query` for reads, `mutation` for writes, `subscription` for real-time streams.\n\n### Input Validation with Zod\n\nAll procedure inputs are validated with Zod schemas. The validated, typed input is available in the procedure handler — no manual parsing.\n\n### Context\n\n`context` is shared state passed to every procedure — auth session, database client, request headers, etc. It is built once per request in a context factory. **Important:** Next.js App Router and Pages Router require separate context factories because App Router handlers receive a fetch `Request`, not a Node.js `NextApiRequest`.\n\n### Middleware\n\nMiddleware chains run before a procedure. Use them for authentication, logging, and request enrichment. They can extend the context for downstream procedures.\n\n---\n\n## How It Works\n\n### Step 1: Install and Initialize\n\n```bash\nnpm install @trpc\u002Fserver @trpc\u002Fclient @trpc\u002Freact-query @tanstack\u002Freact-query zod\n```\n\nCreate the tRPC instance and reusable builders:\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Ftrpc.ts\nimport { initTRPC, TRPCError } from '@trpc\u002Fserver';\nimport { type Context } from '.\u002Fcontext';\nimport { ZodError } from 'zod';\n\nconst t = initTRPC.context\u003CContext>().create({\n  errorFormatter({ shape, error }) {\n    return {\n      ...shape,\n      data: {\n        ...shape.data,\n        zodError:\n          error.cause instanceof ZodError ? error.cause.flatten() : null,\n      },\n    };\n  },\n});\n\nexport const router = t.router;\nexport const publicProcedure = t.procedure;\nexport const middleware = t.middleware;\n```\n\n### Step 2: Define Two Context Factories\n\nNext.js App Router handlers receive a fetch `Request` (not a Node.js `NextApiRequest`), so the context\nmust be built differently depending on the call site. Define one factory per surface:\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Fcontext.ts\nimport { type FetchCreateContextFnOptions } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { auth } from '@\u002Fserver\u002Fauth'; \u002F\u002F Next-Auth v5 \u002F your auth helper\nimport { db } from '.\u002Fdb';\n\n\u002F**\n * Context for the HTTP handler (App Router Route Handler).\n * `opts.req` is the fetch Request — auth is resolved server-side via `auth()`.\n *\u002F\nexport async function createTRPCContext(opts: FetchCreateContextFnOptions) {\n  const session = await auth(); \u002F\u002F server-side auth — no req\u002Fres needed\n  return { session, db, headers: opts.req.headers };\n}\n\n\u002F**\n * Context for direct server-side callers (Server Components, RSC, cron jobs).\n * No HTTP request is involved, so we call auth() directly from the server.\n *\u002F\nexport async function createServerContext() {\n  const session = await auth();\n  return { session, db };\n}\n\nexport type Context = Awaited\u003CReturnType\u003Ctypeof createTRPCContext>>;\n```\n\n### Step 3: Build an Auth Middleware and Protected Procedure\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Ftrpc.ts (continued)\nconst enforceAuth = middleware(({ ctx, next }) => {\n  if (!ctx.session?.user) {\n    throw new TRPCError({ code: 'UNAUTHORIZED' });\n  }\n  return next({\n    ctx: {\n      \u002F\u002F Narrows type: session is non-null from here\n      session: { ...ctx.session, user: ctx.session.user },\n    },\n  });\n});\n\nexport const protectedProcedure = t.procedure.use(enforceAuth);\n```\n\n### Step 4: Create Routers\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Frouters\u002Fpost.ts\nimport { z } from 'zod';\nimport { router, publicProcedure, protectedProcedure } from '..\u002Ftrpc';\nimport { TRPCError } from '@trpc\u002Fserver';\n\nexport const postRouter = router({\n  list: publicProcedure\n    .input(\n      z.object({\n        limit: z.number().min(1).max(100).default(20),\n        cursor: z.string().optional(),\n      })\n    )\n    .query(async ({ ctx, input }) => {\n      const posts = await ctx.db.post.findMany({\n        take: input.limit + 1,\n        cursor: input.cursor ? { id: input.cursor } : undefined,\n        orderBy: { createdAt: 'desc' },\n      });\n      const nextCursor =\n        posts.length > input.limit ? posts.pop()!.id : undefined;\n      return { posts, nextCursor };\n    }),\n\n  byId: publicProcedure\n    .input(z.object({ id: z.string() }))\n    .query(async ({ ctx, input }) => {\n      const post = await ctx.db.post.findUnique({ where: { id: input.id } });\n      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });\n      return post;\n    }),\n\n  create: protectedProcedure\n    .input(\n      z.object({\n        title: z.string().min(1).max(200),\n        body: z.string().min(1),\n      })\n    )\n    .mutation(async ({ ctx, input }) => {\n      return ctx.db.post.create({\n        data: { ...input, authorId: ctx.session.user.id },\n      });\n    }),\n\n  delete: protectedProcedure\n    .input(z.object({ id: z.string() }))\n    .mutation(async ({ ctx, input }) => {\n      const post = await ctx.db.post.findUnique({ where: { id: input.id } });\n      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });\n      if (post.authorId !== ctx.session.user.id)\n        throw new TRPCError({ code: 'FORBIDDEN' });\n      return ctx.db.post.delete({ where: { id: input.id } });\n    }),\n});\n```\n\n### Step 5: Compose the Root Router and Export Types\n\n```typescript\n\u002F\u002F src\u002Fserver\u002Froot.ts\nimport { router } from '.\u002Ftrpc';\nimport { postRouter } from '.\u002Frouters\u002Fpost';\nimport { userRouter } from '.\u002Frouters\u002Fuser';\n\nexport const appRouter = router({\n  post: postRouter,\n  user: userRouter,\n});\n\n\u002F\u002F Export the type for the client — never import the appRouter itself on the client\nexport type AppRouter = typeof appRouter;\n```\n\n### Step 6: Mount the API Handler (Next.js App Router)\n\nThe App Router handler must use `fetchRequestHandler` and the **fetch-based** context factory.\n`createTRPCContext` receives `FetchCreateContextFnOptions` (with a fetch `Request`), not\na Pages Router `req\u002Fres` pair.\n\n```typescript\n\u002F\u002F src\u002Fapp\u002Fapi\u002Ftrpc\u002F[trpc]\u002Froute.ts\nimport { fetchRequestHandler } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { type FetchCreateContextFnOptions } from '@trpc\u002Fserver\u002Fadapters\u002Ffetch';\nimport { appRouter } from '@\u002Fserver\u002Froot';\nimport { createTRPCContext } from '@\u002Fserver\u002Fcontext';\n\nconst handler = (req: Request) =>\n  fetchRequestHandler({\n    endpoint: '\u002Fapi\u002Ftrpc',\n    req,\n    router: appRouter,\n    \u002F\u002F opts is FetchCreateContextFnOptions — req is the fetch Request\n    createContext: (opts: FetchCreateContextFnOptions) => createTRPCContext(opts),\n  });\n\nexport { handler as GET, handler as POST };\n```\n\n### Step 7: Set Up the Client (React Query)\n\n```typescript\n\u002F\u002F src\u002Futils\u002Ftrpc.ts\nimport { createTRPCReact } from '@trpc\u002Freact-query';\nimport type { AppRouter } from '@\u002Fserver\u002Froot';\n\nexport const trpc = createTRPCReact\u003CAppRouter>();\n```\n\n```typescript\n\u002F\u002F src\u002Fapp\u002Fproviders.tsx\n'use client';\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query';\nimport { httpBatchLink } from '@trpc\u002Fclient';\nimport { useState } from 'react';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function TRPCProvider({ 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          headers: () => ({ 'x-trpc-source': 'react' }),\n        }),\n      ],\n    })\n  );\n\n  return (\n    \u003Ctrpc.Provider client={trpcClient} queryClient={queryClient}>\n      \u003CQueryClientProvider client={queryClient}>{children}\u003C\u002FQueryClientProvider>\n    \u003C\u002Ftrpc.Provider>\n  );\n}\n```\n\n---\n\n## Examples\n\n### Example 1: Fetching Data in a Component\n\n```typescript\n\u002F\u002F components\u002FPostList.tsx\n'use client';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function PostList() {\n  const { data, isLoading, error } = trpc.post.list.useQuery({ limit: 10 });\n\n  if (isLoading) return \u003Cp>Loading…\u003C\u002Fp>;\n  if (error) return \u003Cp>Error: {error.message}\u003C\u002Fp>;\n\n  return (\n    \u003Cul>\n      {data?.posts.map((post) => (\n        \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Example 2: Mutation with Cache Invalidation\n\n```typescript\n'use client';\nimport { trpc } from '@\u002Futils\u002Ftrpc';\n\nexport function CreatePost() {\n  const utils = trpc.useUtils();\n\n  const createPost = trpc.post.create.useMutation({\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch the post list\n      utils.post.list.invalidate();\n    },\n  });\n\n  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n    e.preventDefault();\n    const form = e.currentTarget;\n    const data = new FormData(form);\n    createPost.mutate({\n      title: data.get('title') as string,\n      body: data.get('body') as string,\n    });\n    form.reset();\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" placeholder=\"Title\" required \u002F>\n      \u003Ctextarea name=\"body\" placeholder=\"Body\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={createPost.isPending}>\n        {createPost.isPending ? 'Creating…' : 'Create Post'}\n      \u003C\u002Fbutton>\n      {createPost.error && \u003Cp>{createPost.error.message}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Example 3: Server-Side Caller (Server Components \u002F SSR)\n\nUse `createServerContext` — the dedicated server-side factory — so that `auth()` is called\ncorrectly without needing a synthetic or empty request object:\n\n```typescript\n\u002F\u002F app\u002Fposts\u002Fpage.tsx (Next.js Server Component)\nimport { appRouter } from '@\u002Fserver\u002Froot';\nimport { createCallerFactory } from '@trpc\u002Fserver';\nimport { createServerContext } from '@\u002Fserver\u002Fcontext';\n\nconst createCaller = createCallerFactory(appRouter);\n\nexport default async function PostsPage() {\n  \u002F\u002F Uses createServerContext — calls auth() server-side, no req\u002Fres cast needed\n  const caller = createCaller(await createServerContext());\n  const { posts } = await caller.post.list({ limit: 20 });\n\n  return (\n    \u003Cul>\n      {posts.map((post) => (\n        \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Example 4: Real-Time Subscriptions (WebSocket)\n\n```typescript\n\u002F\u002F server\u002Frouters\u002Fnotifications.ts\nimport { observable } from '@trpc\u002Fserver\u002Fobservable';\nimport { EventEmitter } from 'events';\n\nconst ee = new EventEmitter();\n\nexport const notificationRouter = router({\n  onNew: protectedProcedure.subscription(({ ctx }) => {\n    return observable\u003C{ message: string; at: Date }>((emit) => {\n      const onNotification = (data: { message: string }) => {\n        emit.next({ message: data.message, at: new Date() });\n      };\n\n      const channel = `user:${ctx.session.user.id}`;\n      ee.on(channel, onNotification);\n      return () => ee.off(channel, onNotification);\n    });\n  }),\n});\n```\n\n```typescript\n\u002F\u002F Client usage — requires wsLink in the client config\ntrpc.notification.onNew.useSubscription(undefined, {\n  onData(data) {\n    toast(data.message);\n  },\n});\n```\n\n---\n\n## Best Practices\n\n- ✅ **Export only `AppRouter` type** from server code — never import `appRouter` on the client\n- ✅ **Use separate context factories** — `createTRPCContext` for the HTTP handler, `createServerContext` for Server Components and callers\n- ✅ **Validate all inputs with Zod** — never trust raw `input` without a schema\n- ✅ **Split routers by domain** (posts, users, billing) and merge in `root.ts`\n- ✅ **Extend context in middleware** rather than querying the DB multiple times per request\n- ✅ **Use `utils.invalidate()`** after mutations to keep the cache fresh\n- ❌ **Don't cast context with `as any`** to silence type errors — the mismatch will surface as a runtime failure when auth or session lookups return undefined\n- ❌ **Don't use `createContext({} as any)`** in Server Components — use `createServerContext()` which calls `auth()` directly\n- ❌ **Don't put business logic in the route handler** — keep it in the procedure or a service layer\n- ❌ **Don't share the tRPC client instance globally** — create it per-provider to avoid stale closures\n\n---\n\n## Security & Safety Notes\n\n- Always enforce authorization in `protectedProcedure` — never rely on client-side checks alone\n- Validate all input shapes with Zod, including pagination cursors and IDs, to prevent injection via malformed inputs\n- Avoid exposing internal error details to clients — use `TRPCError` with a public-safe `message` and keep stack traces server-side only\n- Rate-limit public procedures using middleware to prevent abuse\n\n---\n\n## Common Pitfalls\n\n- **Problem:** Auth session is `null` in protected procedures even when the user is logged in\n  **Solution:** Ensure `createTRPCContext` uses the correct server-side auth call (e.g. `auth()` from Next-Auth v5) and is not receiving a Pages Router `req\u002Fres` cast via `as any` in an App Router handler\n\n- **Problem:** Server Component caller fails for auth-dependent queries\n  **Solution:** Use `createServerContext()` (the dedicated server-side factory) instead of passing an empty or synthetic object to `createContext`\n\n- **Problem:** \"Type error: AppRouter is not assignable to AnyRouter\"\n  **Solution:** Import `AppRouter` as a `type` import (`import type { AppRouter }`) on the client, not the full module\n\n- **Problem:** Mutations not reflecting in the UI after success\n  **Solution:** Call `utils.\u003Crouter>.\u003Cprocedure>.invalidate()` in `onSuccess` to trigger a refetch via React Query\n\n- **Problem:** \"Cannot find module '@trpc\u002Fserver\u002Fadapters\u002Fnext'\" with App Router\n  **Solution:** Use `@trpc\u002Fserver\u002Fadapters\u002Ffetch` and `fetchRequestHandler` for the App Router; the `nextjs` adapter is for Pages Router only\n\n- **Problem:** Subscriptions not connecting\n  **Solution:** Subscriptions require `splitLink` — route subscriptions to `wsLink` and queries\u002Fmutations to `httpBatchLink`\n\n---\n\n## Related Skills\n\n- `@typescript-expert` — Deep TypeScript patterns used inside tRPC routers and generic utilities\n- `@react-patterns` — React hooks patterns that pair with `trpc.*.useQuery` and `useMutation`\n- `@test-driven-development` — Write procedure unit tests using `createCallerFactory` without an HTTP server\n- `@security-auditor` — Review tRPC middleware chains for auth bypass and input validation gaps\n\n## Additional Resources\n\n- [tRPC Official Docs](https:\u002F\u002Ftrpc.io\u002Fdocs)\n- [create-t3-app](https:\u002F\u002Fcreate.t3.gg) — Production Next.js starter with tRPC wired in\n- [tRPC GitHub](https:\u002F\u002Fgithub.com\u002Ftrpc\u002Ftrpc)\n- [TanStack Query Docs](https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest)\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n",{"name":5,"risk":24,"tags":25,"tools":33,"author":37,"source":38,"category":39,"date_added":40,"description":7},"none",[26,27,28,29,30,31,32],"typescript","trpc","api","fullstack","nextjs","react","type-safety",[34,35,36],"claude","cursor","gemini","suhaibjanjua","community","framework","2026-03-17","main","87afe6ce96220accf50a345dea46784c8c9f210e","YYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftrpc-fullstack",0,"https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base","https:\u002F\u002Fkb.yyc3.top\u002F","https:\u002F\u002Fgithub.com\u002FYYC-Cube","万象归元于云枢 | 深栈智启新纪元","2026-11-10 10:37:32",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill trpc-fullstack",{"tool":56,"command":57},"bunx","bunx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill trpc-fullstack",{"tool":59,"command":60},"pnpm","pnpm dlx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill trpc-fullstack","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftrpc-fullstack\u002FSKILL.md",true,[],[65,72,79,86,93,100],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M7CTDWHERCXPQCP0K03A86AS","vercel-sandbox","Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include \"Vercel Sandbox browser\", \"microVM Chrome\", \"agent-browser in sandbox\", \"browser automation on Vercel\", or any task requiring Chrome in a Vercel Sandbox.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fvercel-sandbox\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":19},"01M7CTD8D2BNJY54S65M47HEJA","slack","Interact with Slack workspaces using browser automation. Use when the user needs to check unread channels, navigate Slack, send messages, extract data, find information, search conversations, or automate any Slack task. Triggers include \"check my Slack\", \"what channels have unreads\", \"send a message to\", \"search Slack for\", \"extract from Slack\", \"find who said\", or any task requiring programmatic Slack interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fslack\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M7CTCSSB8ZP3Q58C3YZDZK1W","electron","Automate Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) using agent-browser via Chrome DevTools Protocol. Use when the user needs to interact with an Electron app, automate a desktop app, connect to a running app, control a native app, or test an Electron application. Triggers include \"automate Slack app\", \"control VS Code\", \"interact with Discord app\", \"test this Electron app\", \"connect to desktop app\", or any task requiring automation of a native Electron application.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Felectron\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":19},"01M7CTCBJY76688V4DCZGWFHGQ","dogfood","Systematically explore and test a web application to find bugs, UX issues, and other problems. Use when asked to \"dogfood\", \"QA\", \"exploratory test\", \"find issues\", \"bug hunt\", \"test this app\u002Fsite\u002Fplatform\", or review the quality of a web application. Produces a structured report with full reproduction evidence -- step-by-step screenshots, repro videos, and detailed repro steps for every issue -- so findings can be handed directly to the responsible teams.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fdogfood\u002FSKILL.md",[],[],{"skillNo":94,"name":95,"nameZh":6,"description":96,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":99,"publishedAt":19},"01M7CTC4H0FFGVEAQR9PA4GA1A","agent-browser","Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fagent-browser\u002FSKILL.md",[],[],{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":19},"01M7CTBKVFYBG78FDSABSDJTTQ","shadcn","Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn\u002Fui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", \"switch to --preset\", and for building a design system or applying a DESIGN.md or brand spec to shadcn\u002Fui.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-UI-界面组件\u002FYYC3-UI-界面组件-ui\u002Fskills\u002Fshadcn\u002FSKILL.md",[],[]]