tRPC
无需 Schema 文件或代码生成,即可在 TypeScript 全栈应用中构建并使用端到端类型安全 API 的框架。
tRPC(TypeScript Remote Procedure Call)是面向 TypeScript 全栈开发者的轻量级 API 框架,通过 TypeScript 类型推断实现零 Schema、零代码生成的端到端类型安全。v11 带来 RSC 原生支持、流式响应和 SSE 订阅。GitHub 35,000+ Stars,npm 周下载量超 70 万次,是 create-t3-app 技术栈的核心支柱。
tRPC
无需 Schema 文件或代码生成,即可在 TypeScript 全栈应用中构建并使用端到端类型安全 API 的框架。
技术简介说明
tRPC(TypeScript Remote Procedure Call)是一个面向 TypeScript 全栈开发者的轻量级 API 框架。它通过 TypeScript 的类型推断机制,在不依赖任何 Schema 文件、代码生成工具或自定义 DSL 的情况下,实现从后端路由定义到前端调用的完整类型安全链路。开发者只需在后端定义一次 procedure,前端即可自动获得完整的类型提示——包括输入参数类型、返回值类型、错误类型等。
与 GraphQL 需要维护 .graphql Schema、REST 需要手写 OpenAPI 文档不同,tRPC 将 TypeScript 编译器本身作为"契约"。后端 router 的类型定义即为 API 契约,前端通过泛型引用后端 router 类型即可获得端到端类型安全。这意味着当后端修改了某个接口的输入或输出结构时,前端会在编译时立即报错,无需等待运行时发现问题。
自 2021 年诞生以来,tRPC 已从一个实验性项目成长为 TypeScript 全栈生态中最具影响力的工具之一。2025 年 3 月发布的 v11 版本带来了 React Server Components 原生支持、流式响应、SSE 订阅、非 JSON 内容类型支持等重大特性,进一步巩固了其在 TypeScript 全栈开发中的地位。截至 2026 年初,tRPC 在 GitHub 上拥有超过 35,000 颗星,npm 周下载量突破 70 万次,Discord 社区成员超过 5,000 人,已成为 create-t3-app 技术栈的核心支柱之一。
基本信息
| 项目 | 详情 |
|---|---|
| 名称 | tRPC |
| 全称 | TypeScript Remote Procedure Call |
| 官网 | https://trpc.io |
| 文档 | https://trpc.io/docs |
| GitHub | https://github.com/trpc/trpc |
| npm | @trpc/server、@trpc/client、@trpc/react-query、@trpc/next |
| License | MIT |
| 最新版本 | v11.x(2025 年 3 月 21 日发布,持续迭代中,截至 2026 年 4 月已更新至 v11.17.0) |
| 维护者 | KATT(Alex Johansson)、sachinraja 及社区贡献者 |
| GitHub Stars | 35,000+(截至 2025 年初) |
| npm 周下载量 | 700,000+(截至 2025 年初) |
| Discord 社区 | 5,000+ 成员 |
| 语言 | TypeScript |
| 运行环境 | Node.js 18+、React 18.2+、TypeScript 5.7.2+ |
| 生态核心 | create-t3-app、TanStack Query、Next.js、Zod |
快速上手
安装
根据项目需求安装相关包。tRPC 分为服务端包和客户端包,客户端根据使用方式不同分为多个集成包:
# pnpm(推荐)
pnpm add @trpc/server @trpc/client zod
# npm
npm install @trpc/server @trpc/client zod
# yarn
yarn add @trpc/server @trpc/client zod如需 React / React Query 集成:
# pnpm
pnpm add @trpc/react-query @tanstack/react-query
# npm
npm install @trpc/react-query @tanstack/react-query如需 Next.js 集成:
# pnpm
pnpm add @trpc/next
# npm
npm install @trpc/next可选的序列化工具(推荐,用于处理 Date、Map、Set 等类型):
pnpm add superjson基础配置
1. 初始化 tRPC Router
// server/trpc.ts
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
// 初始化 tRPC 实例
const t = initTRPC.create()
// 创建基础 router
export const router = t.router
// 创建基础 procedure(未认证的公共过程)
export const publicProcedure = t.procedure2. 定义 Router 和 Procedure
// server/routers/_app.ts
import { z } from 'zod'
import { router, publicProcedure } from '../trpc'
export const appRouter = router({
// 定义一个 query
greeting: publicProcedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return `Hello, ${input.name}!`
}),
// 定义一个 mutation
createPost: publicProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string(),
published: z.boolean().optional(),
}))
.mutation(({ input }) => {
// 执行创建逻辑
return { id: '1', ...input, createdAt: new Date() }
}),
// 嵌套 router
user: router({
getById: publicProcedure
.input(z.string())
.query(({ input }) => {
return { id: input, name: 'John Doe', email: '[email protected]' }
}),
}),
})
// 导出 router 类型供客户端使用
export type AppRouter = typeof appRouter3. 创建服务端适配器(HTTP 服务器)
// server/index.ts
import { createHTTPServer } from '@trpc/server/adapters/standalone'
import { appRouter } from './routers/_app'
const server = createHTTPServer({
router: appRouter,
})
server.listen(3000)
console.log('tRPC server listening on http://localhost:3000')4. 创建客户端
// client/index.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from '../server/routers/_app'
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
}),
],
})
// 使用——完整类型推断!
async function main() {
const greeting = await trpc.greeting.query({ name: 'World' })
console.log(greeting) // "Hello, World!"
const post = await trpc.createPost.mutate({
title: 'My First Post',
content: 'This is my first post content.',
})
console.log(post)
const user = await trpc.user.getById.query('user_123')
console.log(user)
}
main()最小示例(完整可用的服务端 + 客户端)
以下是一个完整可运行的最小示例,展示端到端类型安全的核心能力:
// ===== 服务端 (server.ts) =====
import { initTRPC } from '@trpc/server'
import { createHTTPServer } from '@trpc/server/adapters/standalone'
import { z } from 'zod'
const t = initTRPC.create()
const appRouter = t.router({
// 健康检查
health: t.procedure.query(() => ({ status: 'ok', timestamp: Date.now() })),
// 带输入验证的查询
getUser: t.procedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input }) => {
// 模拟数据库查询
return {
id: input.id,
name: 'Alice',
email: '[email protected]',
createdAt: new Date(),
}
}),
// 带输入验证的 mutation
createUser: t.procedure
.input(z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
}))
.mutation(async ({ input }) => {
// 模拟创建用户
return {
id: crypto.randomUUID(),
...input,
createdAt: new Date(),
}
}),
})
export type AppRouter = typeof appRouter
createHTTPServer({ router: appRouter }).listen(4000)// ===== 客户端 (client.ts) =====
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from './server'
const client = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:4000' })],
})
async function main() {
// ✅ 类型安全:输入参数自动推断为 { id: string }
const user = await client.getUser.query({ id: '550e8400-e29b-41d4-a716-446655440000' })
console.log(user.name) // TypeScript 知道这是 string
// ❌ 编译时错误:缺少 email 字段
// const newUser = await client.createUser.mutate({ name: 'Bob' })
// ✅ 正确调用
const newUser = await client.createUser.mutate({
name: 'Bob',
email: '[email protected]',
})
console.log(newUser.id) // TypeScript 知道这是 string
// ✅ 返回值类型完全推断
const health = await client.health.query()
console.log(health.status) // "ok"
}
main()核心体验:在上述示例中,当你在客户端输入 client.getUser.query({ 时,IDE 会自动提示需要传入 { id: string }。返回值 user 的类型也完全由服务端定义推导而来——无需任何代码生成、Schema 文件或类型声明文件。
核心概念与架构
1. Router(路由器)
Router 是 tRPC 的核心组织单元,类似 Express 的 Router。它是一组 procedure 的集合,支持嵌套和组合。
const appRouter = t.router({
// 顶层 procedure
health: t.procedure.query(() => 'ok'),
// 嵌套 router
post: t.router({
list: t.procedure.query(() => []),
byId: t.procedure.input(z.string()).query(({ input }) => input),
}),
// v11 新增:简写 router(shorthand router)
comment: {
list: t.procedure.query(() => []),
create: t.procedure.mutation(() => null),
},
})Router 本身不是类实例,而是纯粹的 TypeScript 类型——这意味着它只在编译时存在,运行时无额外开销。
2. Procedure(过程)
Procedure 是 tRPC 中最小的可调用单元,分为三种类型:
| 类型 | 用途 | 语义 |
|---|---|---|
query | 读取数据 | 幂等、可缓存 |
mutation | 写入/修改数据 | 非幂等、不缓存 |
subscription | 实时数据流 | 持续推送 |
每个 procedure 都可以配置:
- input:使用 Zod 验证器定义输入 Schema,自动推断类型
- output:可选的输出验证器,在运行时验证返回值
- middleware:中间件链,用于认证、日志、权限控制等
- handler:实际的业务逻辑函数
const procedure = t.procedure
.input(z.object({ id: z.string() })) // 输入验证
.use(authMiddleware) // 中间件
.use(logMiddleware) // 链式中间件
.query(async ({ input, ctx }) => { // 处理器
return await ctx.db.user.findUnique({ where: { id: input.id } })
})3. Input / Output Inference(输入/输出推断)
tRPC 最强大的特性之一是其类型推断链。通过 Zod schema 定义输入,TypeScript 编译器会自动推断出:
- 客户端调用时所需的参数类型
- 服务端 handler 中
input的类型 - 客户端接收到的返回值类型
// 服务端
const getUser = t.procedure
.input(z.object({
id: z.string(),
includePosts: z.boolean().optional(),
}))
.output(z.object({
id: z.string(),
name: z.string(),
posts: z.array(z.object({ id: z.string(), title: z.string() })).optional(),
}))
.query(async ({ input }) => { /* ... */ })
// 客户端 —— 所有类型自动推断
const result = await trpc.getUser.query({
id: '123', // ✅ TypeScript 提示需要 string
includePosts: true, // ✅ TypeScript 提示 boolean | undefined
})
result.name // ✅ string
result.posts // ✅ { id: string, title: string }[] | undefined4. Context(上下文)
Context 是每个请求的共享数据载体,通常包含数据库连接、当前用户信息、session 等。Context 在每次请求开始时由 createContext 函数创建。
// 定义 context 类型
export const createTRPCContext = async (opts: { req: Request }) => {
const session = await getSession(opts.req)
return {
session,
db: prisma,
}
}
// 在中间件中使用 context
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
session: ctx.session, // 收窄类型:session 此时一定存在
},
})
})
// 创建需要认证的 procedure
const protectedProcedure = t.procedure.use(isAuthed)5. Middleware(中间件)
中间件是在 procedure handler 执行前运行的函数链。它用于:
- 认证和授权
- 请求日志
- 错误处理
- 修改 context
中间件遵循洋葱模型,可以在 handler 执行前后都执行逻辑:
const loggingMiddleware = t.middleware(async ({ ctx, next, rawInput }) => {
const start = Date.now()
// 执行下一个中间件或 handler
const result = await next()
const duration = Date.now() - start
console.log(`[${result.ok ? 'OK' : 'ERR'}] ${duration}ms`)
return result
})6. Link(链接)
Link 是客户端的概念,定义了请求如何从客户端传输到服务端。tRPC 提供多种内置 link:
| Link | 用途 |
|---|---|
httpLink | 单次 HTTP 请求 |
httpBatchLink | 批量合并多个请求为一次 HTTP 调用 |
httpBatchStreamLink | 支持流式响应的批量 link(v11 新增) |
httpSubscriptionLink | SSE 订阅 |
wsLink | WebSocket 连接 |
splitLink | 根据操作类型路由到不同 link |
retryLink | 自动重试失败的请求(v11 新增) |
loggerLink | 开发调试日志 |
const client = createTRPCClient<AppRouter>({
links: [
loggerLink(),
httpBatchStreamLink({ // v11 推荐的流式批量 link
url: '/api/trpc',
}),
],
})7. Transformer(转换器)
Transformer 用于在客户端和服务端之间序列化/反序列化非标准 JSON 类型(如 Date、Map、Set、BigInt 等)。tRPC 推荐使用 superjson:
import superjson from 'superjson'
const t = initTRPC.create({
transformer: superjson,
})
// 现在 Date 等类型可以正确序列化
const getPost = t.procedure.query(() => ({
id: '1',
createdAt: new Date(), // 客户端收到的是 Date 对象,不是字符串
}))v11 变更:Transformer 不再在
initTRPC中全局配置,而是移到 link 配置中,使客户端和服务端可以独立决定使用哪种 transformer。
核心特性
1. 端到端类型安全(End-to-End Type Safety)
tRPC 的核心价值。通过 TypeScript 的泛型和类型推断,从服务端 router 定义到客户端调用,所有输入、输出、错误类型在整个链路上完全类型安全。任何 API 变更导致的类型不匹配都会在编译时被捕获,而非运行时。
这是 tRPC 区别于所有其他 API 方案的根本特性——不需要维护独立的 Schema 文件,不需要代码生成步骤,TypeScript 编译器本身就是你的类型系统。
2. 零 Schema / 零代码生成(No Schema, No Codegen)
与 GraphQL 需要维护 .graphql 文件、REST 需要维护 OpenAPI 文档不同,tRPC 完全不需要任何中间表示层。你写的 TypeScript 代码本身就是唯一的"契约"。这意味着:
- 没有
codegen构建步骤 - 没有 Schema 同步问题
- 没有类型与实现不一致的风险
- 更少的构建工具和配置
3. React Server Components 原生支持(v11)
v11 引入了全新的 RSC(React Server Components)集成,支持在服务端组件中直接调用 tRPC procedure,并通过 prefetch 机制将数据无缝水合到客户端 React Query 缓存中。
// 服务端组件 —— 直接 prefetch
export default async function Page() {
void trpc.posts.list.prefetch() // 在服务端立即发起请求
return (
<HydrateClient>
<PostList /> {/* 客户端组件通过 React Query 缓存获取数据 */}
</HydrateClient>
)
}4. 流式响应(Streaming Responses)
v11 新增 httpBatchStreamLink,支持通过 async generator 流式传输查询和 mutation 结果。适用于大数据集、AI 生成内容等场景:
const generateContent = t.procedure
.input(z.object({ prompt: z.string() }))
.query(async function* ({ input }) {
for await (const chunk of generateAIContent(input.prompt)) {
yield chunk // 客户端实时接收每个数据块
}
})5. Server-Sent Events(SSE)订阅
v11 原生支持 SSE 作为 WebSocket 的轻量替代方案用于订阅场景:
const onNotification = t.procedure.subscription(async function* ({ ctx }) {
for await (const event of ctx.notificationStream()) {
yield event // 通过 SSE 推送到客户端
}
})无需维护长连接、无需处理重连逻辑,SSE 基于标准 HTTP,天然支持负载均衡和 CDN。
6. 非 JSON 内容类型支持(v11)
v11 扩展了 procedure 的输入类型,支持 FormData、Blob、File、Uint8Array 等非 JSON 数据。这使得文件上传、二进制流等场景变得原生可用:
const uploadFile = t.procedure
.input(z.instanceof(FormData))
.mutation(async ({ input }) => {
const file = input.get('file') as File
// 处理文件上传
return { name: file.name, size: file.size }
})7. 请求批处理(Request Batching)
tRPC 客户端默认将同一"tick"内的多个请求合并为一次 HTTP 调用。当页面同时发起 5 个查询时,实际只发送 1 次 HTTP 请求。这显著减少了网络往返延迟。
// 以下 3 个查询会被自动合并为 1 次 HTTP 请求
const [user, posts, comments] = await Promise.all([
trpc.user.getById.query('123'),
trpc.post.list.query(),
trpc.comment.list.query(),
])8. 完整的 React Query 集成
通过 @trpc/react-query 包,tRPC 与 TanStack Query v5 深度集成,提供 useQuery、useMutation、useSuspenseQuery 等 React hooks,自动管理缓存、重新获取、乐观更新等:
function UserList() {
const usersQuery = trpc.user.list.useQuery()
const createUserMutation = trpc.user.create.useMutation({
onSuccess: () => {
utils.user.list.invalidate() // 自动刷新用户列表
},
})
if (usersQuery.isLoading) return <Spinner />
return <List users={usersQuery.data} />
}9. 简写 Router 与懒加载(v11)
v11 引入了简写 router 语法和 lazyLoad 支持,使得大型应用的路由组织更加灵活:
// 简写 router
const appRouter = t.router({
post: {
list: t.procedure.query(() => []),
byId: t.procedure.input(z.string()).query(({ input }) => input),
},
})
// 懒加载 router(按需加载,减少冷启动开销)
const appRouter = t.router({
admin: t.router.lazyLoad(() => import('./admin/router')),
})10. 多框架适配器
tRPC 服务端提供多种适配器,支持不同的运行环境:
- Fetch API 适配器:适用于 Next.js App Router、Remix、Cloudflare Workers 等
- Standalone 适配器:适用于独立 Node.js HTTP 服务器
- Express 适配器:集成到 Express 应用
- Fastify 适配器:集成到 Fastify 应用
- AWS Lambda 适配器:无服务器部署
- NestJS 适配器:通过
nestjs-trpc第三方包
生态图
核心生态
┌─────────────────────────────────────────────────────────────────┐
│ tRPC 生态全景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ Zod │ │ TanStack Query │ │ superjson │ │
│ │ 输入验证 │ │ 缓存 / 状态管理 │ │ 序列化 │ │
│ └──────────────┘ └──────────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ Next.js │ │ React │ │ TypeScript │ │
│ │ 全栈框架 │ │ UI 框架 │ │ 类型系统 │ │
│ └──────────────┘ └──────────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ Prisma │ │ create-t3-app │ │ Drizzle │ │
│ │ ORM │ │ 脚手架工具 │ │ 轻量 ORM │ │
│ └──────────────┘ └──────────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ NestJS │ │ Elysia.js │ │ Hono │ │
│ │ 企业框架 │ │ Bun 框架 │ │ 轻量框架 │ │
│ └──────────────┘ └──────────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
关键集成说明
| 技术 | 集成方式 | 说明 |
|---|---|---|
| Next.js | @trpc/next 或 Fetch 适配器 | 官方集成,支持 App Router + RSC,也兼容 Pages Router |
| TanStack Query | @trpc/react-query | v11 正式支持 v5,提供完整的缓存、乐观更新、Suspense 支持 |
| Prisma | 在 context 中注入 | 最常见的 ORM 集成,通过 context 将 Prisma Client 传递给 procedure |
| Zod | procedure.input() | tRPC 默认推荐的验证库,提供 Schema 定义和类型推断 |
| create-t3-app | 内置模板 | T3 Stack(tRPC + Next.js + Prisma + TanStack Query)的官方脚手架 |
| NestJS | nestjs-trpc | 第三方集成,支持装饰器语法和依赖注入 |
| Express / Fastify | 官方适配器 | 直接嵌入现有 Express 或 Fastify 应用 |
| NextAuth.js | 在 createContext 中集成 | 通过 context 注入 session,在 middleware 中做认证 |
| Clerk | 在 createContext 中集成 | 第三方认证服务的常见集成方式 |
create-t3-app 技术栈
create-t3-app 是 tRPC 生态中最重要的脚手架工具,它预设了经过生产验证的技术栈组合:
create-t3-app 默认栈:
├── Next.js (App Router) — 全栈框架
├── tRPC — 类型安全 API 层
├── Prisma — ORM
├── TanStack Query — 客户端缓存/状态管理
├── NextAuth.js — 认证
├── Zod — Schema 验证
├── Tailwind CSS — 样式
└── TypeScript — 类型系统
适用场景
1. TypeScript 全栈 Monorepo
最佳场景。当你的前端和后端都在同一个 monorepo 中,使用相同的 TypeScript 版本时,tRPC 能提供最大的价值。零成本实现端到端类型安全,无需维护 API 文档。
典型技术栈:Next.js + tRPC + Prisma + TanStack Query
2. 内部工具和管理后台
内部工具通常只有一个前端团队,API 的消费者对不需要外部兼容性。tRPC 的类型推断可以极大加速开发效率,减少前后端联调成本。
3. 快速原型和 MVP
tRPC 的零 Schema 特性意味着你可以在几分钟内定义一个完整的类型安全 API。对于需要快速验证想法的 MVP 项目,tRPC 可以显著缩短从构思到产品的时间。
4. 需要频繁迭代的 SaaS 产品
SaaS 产品的 API 经常变化。tRPC 的编译时类型检查确保了每次 API 变更都能立即发现所有受影响的调用点,降低了迭代风险。
5. 实时数据应用
通过 WebSocket 或 SSE 订阅,tRPC 适用于需要实时数据推送的应用——聊天、通知、实时仪表盘等。v11 的 SSE 订阅使得实时场景的部署更加简单。
6. 微前端 / 微服务内部的类型安全通信
在 monorepo 中的微服务之间,如果都使用 TypeScript,tRPC 可以作为轻量级的服务间通信协议,提供类型安全的 RPC 调用。
7. AI / LLM 驱动的流式应用
v11 的流式响应和 async generator 支持使得 tRPC 非常适合 AI 内容生成场景——流式返回 LLM 输出、实时更新 UI。
开发与工程化
开发流程
开发环境搭建
# 使用 create-t3-app 快速开始(推荐)
npx create-t3-app@latest
# 或手动配置
mkdir my-trpc-app && cd my-trpc-app
pnpm init
pnpm add @trpc/server @trpc/client zod
pnpm add -D typescript @types/node tsx开发服务器
# 使用 tsx 运行开发服务器(支持热重载)
tsx watch server/index.ts
# 或使用 ts-node-dev
ts-node-dev --respawn --transpile-only server/index.ts类型检查
# 全量类型检查
tsc --noEmit
# 或使用项目配置的 verify 命令
pnpm typecheck构建
tRPC 本身不需要构建步骤——它直接消费 TypeScript 源码。你的项目构建流程与是否使用 tRPC 无关。常见的构建方式:
- Next.js:
next build(tRPC 集成在其中透明工作) - 独立服务器:
tsc编译后用node运行,或使用tsx直接运行 - Monorepo(Turborepo):
turbo build
测试
tRPC 提供了 createCallerFactory,用于在服务端直接创建 router 的调用者(caller),方便进行集成测试:
import { test, expect } from 'vitest'
import { createCallerFactory } from './trpc'
import { appRouter } from './routers/_app'
const createCaller = createCallerFactory(appRouter)
test('getUser returns user data', async () => {
const caller = createCaller({
session: { user: { id: 'test-user' } },
db: mockDb,
})
const user = await caller.getUser({ id: 'test-user' })
expect(user).toBeDefined()
expect(user.name).toBe('Alice')
})CI/CD
tRPC 项目的 CI/CD 与其他 TypeScript 项目无异:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck # 类型检查——tRPC 端到端类型安全在这里验证
- run: pnpm lint
- run: pnpm test
- run: pnpm build关键点:
pnpm typecheck是验证 tRPC 端到端类型安全的最后一公里。如果后端修改了 procedure 的输入/输出但前端未同步更新,类型检查会失败。
目录结构建议
my-trpc-app/
├── src/
│ ├── server/
│ │ ├── trpc.ts # tRPC 初始化
│ │ ├── routers/
│ │ │ ├── _app.ts # 根 router
│ │ │ ├── user.ts # 用户模块 router
│ │ │ └── post.ts # 文章模块 router
│ │ ├── middleware/
│ │ │ ├── auth.ts # 认证中间件
│ │ │ └── logging.ts # 日志中间件
│ │ └── context.ts # context 创建
│ ├── client/
│ │ ├── trpc.ts # tRPC 客户端配置
│ │ └── query-client.ts # QueryClient 工厂
│ └── app/ # Next.js App Router 页面
├── package.json
└── tsconfig.json
性能与安全
性能优化
请求批处理
tRPC 客户端的 httpBatchLink 和 httpBatchStreamLink 会自动将同一事件循环 tick 内的请求合并。这通常能减少 50-80% 的网络往返:
// 自动批处理——5 个查询 → 1 次 HTTP 请求
const [a, b, c, d, e] = await Promise.all([
trpc.query1.query(),
trpc.query2.query(),
trpc.query3.query(),
trpc.query4.query(),
trpc.query5.query(),
])流式传输
使用 httpBatchStreamLink 处理大数据集时,客户端可以在服务端还在生成响应时就开始处理已收到的数据块:
// 流式查询——客户端实时处理
for await (const chunk of trpc.generate.query({ prompt: '...' })) {
processChunk(chunk) // 无需等待全部数据
}React Query 缓存策略
合理配置 staleTime 和 gcTime 可以显著减少不必要的网络请求:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 分钟内数据视为新鲜
gcTime: 30 * 60 * 1000, // 30 分钟后回收缓存
refetchOnWindowFocus: false, // 窗口聚焦时不自动重新获取
},
},
})RSC Prefetch
v11 的 prefetch() 方法可以在服务端组件渲染时立即发起请求,避免客户端 waterfall:
// 在 layout 中 prefetch,子组件直接使用缓存
export default async function Layout({ children }) {
void trpc.user.me.prefetch() // 立即发起请求
return children
}性能注意事项
- 批处理链接有
maxURLLength限制(默认无限制),GET 请求过长时可能需要切换到 POST 模式 - 大量小查询的批处理可能增加单次请求的 payload 大小,需要权衡
- WebSocket 订阅的连接数受浏览器限制(HTTP/1.1 通常 6 个/域),建议使用 SSE 或 HTTP/2
安全
输入验证
tRPC 与 Zod 的深度集成是其安全的第一道防线。所有客户端输入必须通过 Zod schema 验证:
// ✅ 所有输入都有严格的 schema 验证
const safeProcedure = t.procedure
.input(z.object({
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(200),
email: z.string().email(),
}))
.query(({ input }) => { /* input 类型已经过验证 */ })认证与授权
通过中间件实现认证和授权,确保每个 procedure 都有适当的权限控制:
// 认证中间件
const isAuthenticated = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: '请先登录' })
}
return next({ ctx: { ...ctx, session: ctx.session } })
})
// 角色授权中间件
const hasRole = (role: string) =>
t.middleware(({ ctx, next }) => {
if (ctx.session.user.role !== role) {
throw new TRPCError({ code: 'FORBIDDEN', message: '权限不足' })
}
return next()
})
// 组合使用
const adminProcedure = t.procedure.use(isAuthenticated).use(hasRole('admin'))CSRF 防护
- tRPC 的 POST 请求会严格验证
Content-Type头(v11 增强) - 建议为 mutation 和 subscription 使用 POST 方法
- 配合 CORS 配置限制允许的源
速率限制
tRPC 本身不内置速率限制,推荐通过中间件或反向代理实现:
const rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {
const key = `rate-limit:${ctx.session?.user?.id ?? ctx.ip}`
const count = await redis.incr(key)
if (count === 1) await redis.expire(key, 60)
if (count > 100) {
throw new TRPCError({ code: 'TOO_MANY_REQUESTS', message: '请求过于频繁' })
}
return next()
})错误信息泄露
生产环境中应避免将内部错误详情暴露给客户端。tRPC 的 TRPCError 允许自定义错误响应:
// 生产环境中捕获内部错误,返回通用错误信息
const t = initTRPC.create({
errorFormatter({ error, shape }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
return {
...shape,
message: '服务器内部错误', // 不暴露内部细节
}
}
return shape
},
})数据脱敏
在 context 或 output validator 中过滤敏感字段,确保不将密码哈希、内部 ID 等数据发送给客户端。
技术对比
tRPC vs GraphQL vs REST vs OpenAPI vs Hono RPC
| 维度 | tRPC | GraphQL | REST | OpenAPI / Swagger | Hono RPC |
|---|---|---|---|---|---|
| 类型安全 | ✅ 端到端,编译时 | ✅ 有类型,但需代码生成 | ❌ 需手写或工具生成 | ✅ Schema 驱动 | ✅ 类似 tRPC |
| 代码生成 | ❌ 不需要 | ✅ 需要(codegen) | 视情况 | 需要生成客户端 | ❌ 不需要 |
| Schema 文件 | ❌ 不需要 | ✅ 需要 .graphql | ❌ 不需要 | ✅ 需要 YAML/JSON | ❌ 不需要 |
| 运行时开销 | 极低 | 中等(查询解析/验证) | 低 | 取决于实现 | 极低 |
| 学习曲线 | 低(TS 开发者) | 中高(GraphQL 语法/概念) | 低 | 中 | 低 |
| 缓存 | HTTP 缓存 + React Query | 复杂,需 DataLoader | ✅ 标准 HTTP 缓存 | 标准 HTTP 缓存 | HTTP 缓存 |
| 实时订阅 | ✅ WebSocket / SSE | ✅ Subscriptions | ❌ 需 SSE/WebSocket | ❌ 需额外方案 | ✅ WebSocket |
| 多客户端支持 | ⚠️ 适合 TypeScript 客户端 | ✅ 任意语言客户端 | ✅ 任意语言客户端 | ✅ 生成多语言客户端 | ⚠️ 适合 TS 客户端 |
| 工具链成熟度 | 中高 | 高(Apollo 生态) | 极高 | 高 | 中 |
| Monorepo 适配 | ✅ 最佳 | 一般 | 一般 | 一般 | ✅ 优秀 |
| RSC 支持 | ✅ 原生(v11) | ⚠️ 需额外适配 | 手动实现 | 手动实现 | ⚠️ 需额外适配 |
| 文件上传 | ✅ v11 原生支持 | ⚠️ 需 multipart 扩展 | ✅ 标准支持 | 取决于实现 | ✅ 支持 |
何时选择 tRPC
- ✅ 前后端都是 TypeScript
- ✅ Monorepo 架构
- ✅ 团队希望最大化开发效率和类型安全
- ✅ 不需要支持非 TypeScript 的外部消费者
何时不选择 tRPC
- ❌ 需要支持多种语言的外部 API 消费者
- ❌ 前后端分离在不同的代码仓库且无法共享类型
- ❌ 已有成熟的 GraphQL 或 REST 生态
- ❌ 需要标准的 HTTP 缓存语义(tRPC 使用 RPC 风格,GET/POST 语义与 REST 不同)
tRPC vs Hono RPC
Hono RPC 是另一个 TypeScript 端到端类型安全方案,与 tRPC 的核心差异:
- Hono RPC 更轻量,基于 Web Standards API,适合边缘计算场景
- tRPC 生态更成熟,React Query 集成更深入,社区更大
- Hono RPC 的类型推导基于链式调用,tRPC 基于 procedure 定义
- 两者都适合 TypeScript monorepo,tRPC 在 React 生态中优势更大
最佳实践
生产环境建议
1. 使用 superjson 处理序列化
默认 JSON 序列化无法正确处理 Date、Map、Set、BigInt 等类型。superjson 是 tRPC 官方推荐的解决方案:
import superjson from 'superjson'
// 服务端 link 配置
const t = initTRPC.create()
// 客户端 link 配置
httpBatchLink({
url: '/api/trpc',
transformer: superjson, // v11 在 link 中配置
})2. 合理组织 Router 结构
大型项目中,按功能模块拆分 router,使用懒加载优化冷启动:
// routers/_app.ts
export const appRouter = t.router({
auth: t.router.lazyLoad(() => import('./auth')),
user: t.router.lazyLoad(() => import('./user')),
post: t.router.lazyLoad(() => import('./post')),
admin: t.router.lazyLoad(() => import('./admin')),
})3. 始终使用 output 验证
虽然 input 验证是必须的,但 output 验证同样重要——它确保后端返回的数据符合预期类型:
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
})
const getUser = t.procedure
.input(z.string())
.output(userSchema) // 运行时验证输出
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input } })
if (!user) throw new TRPCError({ code: 'NOT_FOUND' })
return userSchema.parse(user) // 确保输出符合 schema
})4. 正确使用 React Query 缓存
// 全局默认配置
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000, // 30 秒内视为新鲜
refetchOnWindowFocus: true, // 窗口聚焦时重新获取
retry: 1, // 失败重试 1 次
},
mutations: {
retry: false, // mutation 不重试
},
},
})5. 错误处理策略
建立统一的错误处理策略,区分业务错误和系统错误:
// 定义业务错误类型
class NotFoundError extends TRPCError {
constructor(entity: string, id: string) {
super({
code: 'NOT_FOUND',
message: `${entity} ${id} 不存在`,
})
}
}
class ValidationError extends TRPCError {
constructor(message: string) {
super({
code: 'BAD_REQUEST',
message,
})
}
}
// 使用
const getUser = t.procedure
.input(z.string())
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input } })
if (!user) throw new NotFoundError('用户', input)
return user
})常见陷阱
1. 忘记在客户端使用相同的 Transformer
// ❌ 错误:服务端用了 superjson,客户端忘了
const t = initTRPC.create({ transformer: superjson })
// 客户端没有配置 transformer → Date 会变成字符串
// ✅ 正确:v11 中在 link 中配置
httpBatchLink({ url: '/api/trpc', transformer: superjson })2. 过度依赖批处理
批处理将所有请求合并为一次调用,但如果某个请求很慢,会阻塞所有其他请求。建议:
- 设置合理的批处理超时时间
- 将慢查询从批处理中排除
- 监控批处理的总 payload 大小
3. 在 context 中做过重的初始化
createContext 在每次请求时都会执行,避免在其中执行耗时的数据库连接或外部 API 调用。推荐使用连接池和延迟初始化:
// ✅ 推荐:延迟初始化
export const createContext = async () => {
const db = prisma // Prisma Client 应该复用,不要每次新建
return { db }
}4. 忽略 RSC 的数据所有权问题
在 Next.js App Router 中,直接从服务端组件调用 tRPC procedure 不会经过 React Query 缓存。如果需要缓存,使用 prefetch() + HydrateClient 模式:
// ❌ 错误:直接调用,不走缓存
const data = await trpc.getUser({ id: '123' })
// ✅ 正确:prefetch + HydrateClient
void trpc.getUser.prefetch({ id: '123' })
return (
<HydrateClient>
<UserClientComponent id="123" /> {/* 客户端通过 React Query 缓存获取 */}
</HydrateClient>
)5. 不配置 CORS
前后端分离部署时,CORS 配置容易被忽略。tRPC 的 Fetch 适配器支持通过标准 Web API 配置:
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
responseMeta({ ctx, errors }) {
return {
headers: {
'Access-Control-Allow-Origin': '*', // 生产环境应指定具体域名
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
}
},
})技术局限与边界
已知限制
1. 仅限 TypeScript 生态
tRPC 的核心价值——端到端类型安全——完全依赖 TypeScript 的类型系统。如果客户端不是 TypeScript(如 Python、Go、Swift 等),tRPC 无法提供类型安全保证。虽然可以通过 HTTP 协议直接调用 tRPC 的 API 端点,但会失去所有类型推断优势。
影响:不适合作为面向多语言外部消费者的公共 API。
2. Monorepo 依赖
tRPC 的端到端类型安全要求客户端和服务端共享类型定义。这通常意味着它们必须在同一个 monorepo 中,或者通过某种方式(如 npm 包)共享类型。对于前后端完全独立维护的项目,tRPC 的类型安全优势无法发挥。
3. 不支持标准 HTTP 缓存语义
tRPC 的 RPC 风格意味着查询通常通过 POST 请求发送(虽然也支持 GET),这与标准 HTTP 缓存机制(基于 GET + Cache-Control)不完全兼容。对于需要 CDN 缓存或浏览器原生缓存的场景,这是一个限制。
注:tRPC v11 支持 GET 请求模式,但批处理仍然使用 POST。
4. 调试复杂性
端到端类型推断链虽然强大,但也增加了调试难度。当类型推断出错时,TypeScript 的错误信息可能非常复杂和冗长。对于新手来说,理解 tRPC 的类型机制需要一定时间。
5. 社区生态相比 REST / GraphQL 较小
虽然 tRPC 社区增长迅速,但相比成熟的 REST 工具和 GraphQL(Apollo)生态,可用的中间件、调试工具、监控集成等仍然较少。遇到问题时,可搜索到的社区资源相对有限。
6. React Query v5 强制依赖
v11 要求使用 TanStack Query v5,这是一个有破坏性变更的升级(如 isLoading → isPending)。对于仍在使用 v4 或更早版本的项目,升级到 v11 需要额外的迁移工作。
7. 不支持 Schema 演进
与 GraphQL 和 OpenAPI 不同,tRPC 没有"Schema 版本"的概念。当 API 需要向后兼容地演进时,tRPC 依赖于 TypeScript 的类型兼容性,而不是显式的版本控制。这在某些需要长期维护 API 兼容性的场景中可能是一个限制。
不适用场景
- 公共开放 API:需要支持多种语言的客户端,使用 OpenAPI/Swagger 更合适
- 跨语言微服务通信:gRPC 或 Protocol Buffers 更适合
- 前后端完全分离的独立项目:无法共享类型定义,类型安全无法传递
- 已有大量 GraphQL 基础设施的团队:迁移成本高,收益不确定
- 简单的 CRUD 应用:对于非常简单的应用,tRPC 的类型安全优势可能不足以抵消其引入的复杂度
学习资源
官方资源
| 资源 | 链接 | 说明 |
|---|---|---|
| 官方文档 | trpc.io/docs | 最权威的参考,持续更新 |
| 官方博客 | trpc.io/blog | v11 发布公告、新功能介绍 |
| GitHub 仓库 | github.com/trpc/trpc | 源码、Issue、讨论 |
| Discord 社区 | trpc.io/discord | 5,000+ 成员的活跃社区 |
| v10 → v11 迁移指南 | trpc.io/docs/migrate-from-v10-to-v11 | 完整的迁移步骤 |
| 示例仓库 | github.com/trpc/examples-minimal | 最小可运行示例 |
教程与指南
| 资源 | 链接 | 说明 |
|---|---|---|
| tRPC 11 Setup for Next.js App Router 2025 | dev.to/matowang/trpc-11-setup-for-nextjs-app-router-2025 | 2025 年最新的 Next.js App Router + tRPC v11 配置指南 |
| tRPC v11: End-to-End Type Safety Without the Schema Tax | Medium | v11 深度解析文章 |
| Mastering tRPC with RSC: 2026 Guide | christadrian.dev | React Server Components + tRPC 权威指南 |
| tRPC v11 + Next.js App Router | dev.to/whoffagents | 实战配置教程 |
| NestJS tRPC | nestjs-trpc.io | NestJS + tRPC v11 集成文档 |
社区资源
| 资源 | 链接 | 说明 |
|---|---|---|
| 中文文档镜像 | trpc.nodejs.cn | 社区维护的中文翻译 |
| awesome-trpc | GitHub | 社区精选 tRPC 资源合集 |
| Reddit 讨论 | r/reactjs | 2025 年 tRPC 使用体验讨论 |
| YouTube 教程 | tRPC v11 with TanStack Query | 视频教程:v11 + TanStack Query + TanStack Start |
推荐学习路径
- 入门:阅读 官方文档 Quickstart → 运行最小示例
- Next.js 集成:参考 Next.js Integration → 配置 App Router
- RSC 模式:学习 Server Components 配置 → 理解 prefetch + HydrateClient
- 生产实践:研究 create-t3-app 源码 → 学习认证、中间件、错误处理
- 深入理解:阅读 v11 发布公告 → 了解流式、SSE、非 JSON 等新特性
2026 年现状
最新版本
截至 2026 年 7 月,tRPC 的最新稳定版本为 v11.x(npm 上最新为 v11.17.0,发布于 2026 年 4 月)。v11 是一个长期维护版本,持续进行 bug 修复和小功能迭代。
发展趋势
1. 框架无关化
tRPC 在 2025-2026 年明显朝着更加"框架无关"的方向发展。官方文档的改版更加强调 tRPC 作为通用 TypeScript RPC 框架的定位,而非仅仅是 Next.js 的附属工具。对 Express、Fastify、Hono、NestJS 等框架的适配持续改进。
2. React Server Components 深度集成
v11 的 RSC 支持是一个重要的里程碑。随着 Next.js App Router 成为主流,tRPC 的 prefetch() + HydrateClient 模式已经成为 RSC 数据获取的最佳实践之一。社区对 tRPC 在 RSC 场景下的性能优化(如流式渲染、并行预取)给予了高度评价。
3. 流式与实时能力增强
v11 引入的 httpBatchStreamLink 和 SSE 订阅,使 tRPC 在实时数据场景中的竞争力大幅提升。结合 AI/LLM 的流式输出需求,tRPC 正成为构建 AI 驱动应用的重要基础设施。
4. 社区持续增长
从 2025 年初的 35,000 GitHub stars 和 70 万周下载量持续增长,tRPC 已成为 TypeScript 全栈开发的事实标准之一。create-t3-app 的流行度进一步推动了 tRPC 的采用。
5. 与 Server Actions 的共存
社区对"tRPC vs Next.js Server Actions"的讨论在 2025-2026 年持续。共识逐渐形成:
- tRPC 适合需要复杂查询、缓存管理、类型安全 API 层的场景
- Server Actions 适合简单的表单提交和 mutation 场景
- 两者可以在同一项目中互补使用——tRPC 处理查询和复杂数据流,Server Actions 处理简单 mutation
社区活跃度
| 指标 | 数值 | 趋势 |
|---|---|---|
| GitHub Stars | 35,000+ | 📈 持续增长 |
| npm 周下载量 | 700,000+ | 📈 持续增长 |
| Discord 成员 | 5,000+ | 📈 活跃 |
| 贡献者 | 数百人 | 📈 健康 |
| 发布频率 | 每月多次 patch/minor | 📈 维护积极 |
| Issue 响应 | 通常数天内 | ✅ 良好 |
生态展望
tRPC 在 2026 年的定位已经从"TypeScript 全栈的类型安全 API 层"扩展为"现代 Web 应用的数据基础设施"。随着以下趋势的发展,tRPC 的重要性预计会进一步提升:
- TypeScript 的全面普及——越来越多项目采用 TypeScript,tRPC 的类型安全优势随之放大
- Monorepo 架构的流行——Turborepo、Nx 等工具的普及使得 tRPC 的类型共享更加自然
- AI 应用的爆发——流式响应和实时订阅能力使 tRPC 成为 AI 应用数据层的理想选择
- 边缘计算的发展——tRPC 的 Fetch API 适配器天然支持边缘运行时
总结:tRPC 是 2025-2026 年 TypeScript 全栈开发中最重要的 API 框架之一。它通过消除 Schema 文件和代码生成步骤,提供了最简洁的端到端类型安全体验。v11 的发布标志着 tRPC 在 RSC 支持、流式传输、实时订阅等方面达到了新的高度。对于 TypeScript monorepo 项目,tRPC 仍然是实现端到端类型安全的首选方案。