tRPC模式
Mattakushi4322026-10-26查看 31复制 1697
何时激活:tRPC、端到端类型安全、RPC、路由器、过程、中间件、订阅、React Query 集成
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 4直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景
💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。
SKILL.md5739 chars
---
name: trpc-patterns
description: "When to activate: tRPC, end-to-end type safety, RPC, routers, procedures, middleware, subscriptions, React Query integration"
origin: zakr
metadata:
namespace: zakr/devtools-pack
version: 1.0.0
category: engineering
language: typescript
platforms: [claude-code, codex, gemini-cli, cursor, aider, windsurf]
---
# tRPC Patterns
## Server Setup (Next.js App Router)
```ts
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server'
import { getServerSession } from 'next-auth'
import superjson from 'superjson'
import { ZodError } from 'zod'
interface Context {
session: Awaited<ReturnType<typeof getServerSession>> | null
}
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
}
},
})
export const router = t.router
export const publicProc = t.procedure
export const authedProc = t.procedure.use(({ ctx, next }) => {
if (!ctx.session) throw new TRPCError({ code: 'UNAUTHORIZED' })
return next({ ctx: { ...ctx, session: ctx.session } })
})
```
## Router Definition
```ts
// server/routers/users.ts
import { z } from 'zod'
import { router, publicProc, authedProc } from '../trpc'
export const usersRouter = router({
list: publicProc
.input(z.object({ page: z.number().default(1), limit: z.number().max(100).default(20) }))
.query(async ({ input }) => {
const { page, limit } = input
const users = await db.user.findMany({ skip: (page - 1) * limit, take: limit })
return users
}),
byId: publicProc
.input(z.object({ id: z.string().cuid() }))
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input.id } })
if (!user) throw new TRPCError({ code: 'NOT_FOUND' })
return user
}),
create: authedProc
.input(z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
}))
.mutation(async ({ input }) => {
return db.user.create({ data: input })
}),
delete: authedProc
.input(z.object({ id: z.string().cuid() }))
.mutation(async ({ input, ctx }) => {
const user = await db.user.findUnique({ where: { id: input.id } })
if (!user) throw new TRPCError({ code: 'NOT_FOUND' })
if (user.id !== ctx.session.user.id) throw new TRPCError({ code: 'FORBIDDEN' })
return db.user.delete({ where: { id: input.id } })
}),
})
```
## Root Router
```ts
// server/routers/_app.ts
import { router } from '../trpc'
import { usersRouter } from './users'
import { postsRouter } from './posts'
export const appRouter = router({
users: usersRouter,
posts: postsRouter,
})
export type AppRouter = typeof appRouter
```
## Next.js Route Handler
```ts
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/server/routers/_app'
import { createContext } from '@/server/context'
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
})
export { handler as GET, handler as POST }
```
## Client Setup
```ts
// lib/trpc.ts
import { createTRPCReact } from '@trpc/react-query'
import { httpBatchLink } from '@trpc/client'
import superjson from 'superjson'
import type { AppRouter } from '@/server/routers/_app'
export const trpc = createTRPCReact<AppRouter>()
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const queryClient = new QueryClient()
const trpcClient = trpc.createClient({
transformer: superjson,
links: [
httpBatchLink({ url: '/api/trpc' }),
],
})
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
)
}
```
## Usage in Components
```tsx
function UserList() {
const { data, isLoading } = trpc.users.list.useQuery({ page: 1 })
const createUser = trpc.users.create.useMutation({
onSuccess: () => utils.users.list.invalidate(),
})
const utils = trpc.useUtils()
if (isLoading) return <Spinner />
return (
<>
{data?.map(u => <UserRow key={u.id} user={u} />)}
<button onClick={() => createUser.mutate({ name: 'Alice', email: 'a@b.com' })}>
Add
</button>
</>
)
}
```
## Middleware Pattern
```ts
// Logging middleware
const loggedProc = t.procedure.use(async ({ path, type, next }) => {
const start = Date.now()
const result = await next()
const ms = Date.now() - start
console.log(`${type} ${path} - ${ms}ms`)
return result
})
// Rate limiting middleware
const rateLimited = t.procedure.use(async ({ ctx, next }) => {
const key = `ratelimit:${ctx.session?.user.id ?? ctx.ip}`
const ok = await rateLimiter.check(key)
if (!ok) throw new TRPCError({ code: 'TOO_MANY_REQUESTS' })
return next()
})
```
## Subscriptions (WebSocket)
```ts
// Server
import { observable } from '@trpc/server/observable'
import { EventEmitter } from 'events'
const ee = new EventEmitter()
export const notificationsRouter = router({
onNew: authedProc.subscription(({ ctx }) => {
return observable<Notification>(emit => {
const handler = (n: Notification) => {
if (n.userId === ctx.session.user.id) emit.next(n)
}
ee.on('notification', handler)
return () => ee.off('notification', handler)
})
}),
})
// Client
const { data } = trpc.notifications.onNew.useSubscription(undefined, {
onData(notification) { toast(notification.message) },
})
```