tRPC模式

Mattakushi4322026-10-26查看 31复制 1697

何时激活:tRPC、端到端类型安全、RPC、路由器、过程、中间件、订阅、React Query 集成

Download (.md)

📘如何使用这个技能

  1. 1
    复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
  2. 2
    打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
  3. 3
    粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
  4. 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) },
})
```
来源:skillsdirectory

评论0

登录后参与评论

还没有评论,来抢沙发吧