1. 概念 & 快速开始
RPC 是 "远程过程调用" 的缩写。它是一种从另一台计算机(客户端)调用一台计算机(服务器)上的函数的方法。使用传统的 HTTP/REST API,你可以调用 URL 并获得响应。使用 RPC,你可以调用函数并获得响应。
2. 常用术语
| 核心术语 | 描述 |
|---|---|
Procedure | API 端点可以 query、mutation、subscription |
Query | 数据读取(对应 HTTP GET) |
Mutation | 数据修改(对应 HTTP POST/PUT/DELETE) |
Subscription | 实时数据推送(基于 WebSockets) |
use | 中间件应用方法(调用:Procedure 或 Router 实例) |
Router | 组织多个 Procedure 的容器 |
Context | 在每个请求生命周期注入共享数据 |
Middleware | 中间件工厂函数(调用:t实例[initTRPC.create()]) |
3. 关系图
graph TD
A[Router] --> B(Procedure)
B --> C[Query]
B --> D[Mutation]
B --> E[Subscription]
A --> F[Middleware]
F --> G[Context]
G --> H[认证/数据库等]
A --> I[ErrorFormatter]
C & D & E --> J[Resolver]
J --> K[业务逻辑]
4. 后端用法
- 初始化 tRPC
import { initTRPC } from '@trpc/server';
// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
4.1 路由 (router)
4.1.1 定义路由
import { publicProcedure, router } from './trpc';
const appRouter = router({
greeting: publicProcedure.query(() => 'hello tRPC v10!'),
});
export type AppRouter = typeof appRouter;
4.1.2 模块化路由 mergeRouters (多个路由合并)
// user.router.ts
export const userRouter = t.router({
get: t.procedure.input(z.number()).query(({ input }) => /* ... */),
create: t.procedure.input(z.object(/* ... */)).mutation(/* ... */),
});
// post.router.ts
export const postRouter = t.router({
list: t.procedure.query(/* ... */),
delete: t.procedure.input(z.number()).mutation(/* ... */),
});
// 合并路由
import { mergeRouters } from '@trpc/server';
export const appRouter = mergeRouters(
userRouter,
postRouter,
t.router({ health: t.procedure.query(() => 'OK') })
);
4.1.3 动态加载路由 lazy
import { lazy } from '@trpc/server';
import { router } from '../trpc';
export const appRouter = router({
greeting: lazy(() => import('./greeting.js')),
user: lazy(() => import('./user.js').then((m) => m.userRouter)),
});
export type AppRouter = typeof appRouter;
4.2 过程 (Procedure)
4.2.1 过程是暴露给客户端的函数,它可以是以下之一:
- 一个
Query- 用于获取数据,一般不改变任何数据 - 一个
Mutation- 用于发送数据,通常用于创建/更新/删除目的 - 一个
Subscription- 你可能不需要这个,我们有 专用文档
4.2.2 查询 query
- 示例
import { z } from 'zod';
t.router({
// 无输入查询
getUsers: t.procedure.query(() => {
return db.users.findMany();
}),
// 带输入查询
getUser: t.procedure
.input(z.object({ id: z.number() })) // Zod 输入验证
.query(({ input }) => {
return db.users.find(input.id);
}),
});
4.2.3 变更 mutation
- 示例
t.router({
createUser: t.procedure
.input(z.object({
name: z.string(),
email: z.string().email()
}))
.mutation(({ input }) => {
return db.users.create(input);
}),
updateUser: t.procedure
.input(z.object({
id: z.number(),
data: z.object({ name: z.string().optional() })
}))
.mutation(({ input }) => {
return db.users.update(input.id, input.data);
}),
});
4.2.4 实时订阅 subscription
- 示例
import { observable } from '@trpc/server/observable';
t.router({
onMessage: t.procedure
.subscription(() => {
return observable<string>((emit) => {
const onMessage = (msg: string) => emit.next(msg);
socket.on('message', onMessage);
return () => socket.off('message', onMessage);
});
}),
});
4.3 输入和输出验证器 (zod) 请查看zod文章
4.4 上下文 (context)
4.4.1 上下文是每个请求独有的数据容器,在请求开始时创建,在请求结束时销毁。它包含:
- 请求特定信息(认证数据、请求头等)
- 共享资源(数据库连接、日志工具等)
- 中间件生成的附加数据
4.4.2 生命周期
sequenceDiagram
participant Client
participant tRPC
participant Context
Client->>tRPC: 发起请求
tRPC->>Context: 创建上下文实例
Context->>tRPC: 初始化共享资源
tRPC->>Procedure: 执行处理程序
Procedure->>Context: 访问上下文数据
tRPC->>Context: 销毁上下文
tRPC->>Client: 返回响应
4.5 中间件 (use & middleware)
4.5.1 核心区别对比
middleware() | use() | |
|---|---|---|
| 本质 | 中间件工厂函数 | 中间件应用方法 |
| 返回值 | 中间件函数(未激活) | 增强后的 Procedure/Router 实例 |
| 调用对象 | t 实例 (initTRPC.create()) | Procedure 或 Router 实例 |
| 作用阶段 | 定义中间件逻辑 | 将中间件绑定到具体目标 |
| 是否改变原对象 | 否 | 否(返回新实例) |
| 链式调用 | 不支持 | 支持(可连续 .use().use()) |
4.5.2 工作流程图示
graph LR
A[定义中间件] -->|t.middleware| B(中间件函数)
B --> C[应用中间件]
C -->|procedure.use| D[增强的Procedure]
C -->|router.use| E[增强的Router]
D --> F[处理请求]
E --> F
4.5.3 use():中间件应用方法
- 示例
// 应用到 Procedure(创建新实例)
const protectedProcedure = t.procedure.use((opts)=>{
// opts 包含 { ctx, next, ..等 } ctx 是上下文内容, next 表示执行下一个函数
// 验证处理
// 如果不存在相应信息,则抛出错误,阻止后续代码执行
if(!opts.ctx.session?.id){
return new TRPCError({ code: 'error' })
}
// 正常向下顺序执行
return opts.next()
}).query((opts)=>{
return 'hello word'
})
// 应用到 Router(影响所有子过程)
const protectedRouter = t.router(...).use(authMiddleware)
4.5.4 ure(): 后执行代码
- 示例
4.5.5 ure(): 参数 ctx 拓展
- 示例
const protectedProcedure = t.procedure.use((opts)=>{
console.log(`之前未扩展 ctx:`, opts.ctx)
return opts.next({
// 如果 ctx 之前不存在 aaaa,则会添加,其他数据不变
// 如果 ctx 之前存在 aaaa,则只会覆盖这个变量,其他数据不变
// 也就是 如果之前 ctx 有 yyyy 和 aaaa 数据,yyyy数据会一直存在,只会改变 aaaa
ctx: { aaaa: '1231456464' }
})
}).query((opts)=>{
console.log(`扩展后 ctx: `, opts.ctx)
return 'hello word'
})
4.5.6 middleware() 使用
-
描述:当我们想将use()中的函数进行抽离公共,并且有类型提示
-
示例
// 单独抽离出来 也就是 中间件工厂函数
const createAuthMiddleware = (options: { role: string }) => {
return t.middleware(({ ctx, next }) => {
if (ctx.user.role !== options.role) throw ...
return next();
});
};
// 使用
const adminProcedure = t.procedure.use(
createAuthMiddleware({ role: 'admin' })
);
4.5.7 合并多个 procedure(), 使用 unstable_concat()
// 合并多个procedure
const proc3 = proc1.unstable_concat(proc2)
4.5.8 合并多个 middleware(), 使用 unstable_pipe()
// 合并多个 middleware
const mid3 = mid1.unstable_pipe(mid2)
4.6 授权
4.7 错误处理
4.7.1 全局错误处理
// server/trpc.ts
import { TRPCError, initTRPC } from '@trpc/server';
import { ZodError } from 'zod';
import type { Context } from './context';
import { fromZodError } from 'zod-validation-error';
const t = initTRPC.context<Context>().create({
// 全局错误格式化
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
// 添加原始错误信息
originalError: error.cause instanceof Error ? error.cause.message : null,
// 添加 Zod 验证错误详情
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
// 添加时间戳
timestamp: new Date().toISOString(),
}
};
},
});
4.7.2 中间件错误处理
// 错误处理中间件
const errorMiddleware = t.middleware(async ({ next, ctx }) => {
try {
return await next({
ctx: {
...ctx,
// 添加额外上下文
requestId: crypto.randomUUID(),
}
});
} catch (cause) {
// 处理已知错误类型
if (cause instanceof BusinessError) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: cause.message,
cause,
});
}
// 处理 Zod 验证错误
if (cause instanceof ZodError) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: '输入验证失败',
cause: fromZodError(cause),
});
}
// 处理 TRPC 错误
if (cause instanceof TRPCError) {
throw cause;
}
// 处理未知错误
console.error('未处理的错误:', cause);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: '发生内部错误',
cause,
});
}
});
// 带错误处理的过程
export const procedure = t.procedure.use(errorMiddleware);
4.7.3 路由错误处理
// 示例路由
export const appRouter = t.router({
getUser: procedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input, ctx }) => {
// 模拟业务错误
if (input.id === '00000000-0000-0000-0000-000000000000') {
throw new BusinessError('INVALID_USER', '无效的用户ID');
}
// 模拟数据库错误
if (input.id === '11111111-1111-1111-1111-111111111111') {
throw new Error('数据库连接失败');
}
// 正常返回
return { id: input.id, name: 'John Doe', email: 'john@example.com' };
}),
createPost: procedure
.input(z.object({
title: z.string().min(5),
content: z.string().min(10),
tags: z.array(z.string()).max(5),
}))
.mutation(async ({ input }) => {
// 实际应用中保存到数据库
return { ...input, id: crypto.randomUUID(), createdAt: new Date() };
}),
});
export type AppRouter = typeof appRouter;
4.8 订阅
4.8.1 tRPC 订阅的三大组件
-
订阅过程(Subscription Procedure):- 定义客户端如何发起订阅
- 指定推送的数据格式
-
事件发射器(Event Emitter):- 服务器端的事件源
- 负责触发数据更新事件
-
订阅管理器(Subscription Manager):- 跟踪所有活跃订阅
- 管理事件分发
4.8.2 示例
4.9 webSockets
4.10 数据转换器
4.10.1 数据转换器使用场景对比
| 场景 | 推荐转换器 | 优点 | 缺点 |
|---|---|---|---|
| 基本类型处理 | JSON | 简单快速,无需依赖 | 无法处理 Date, Map, Set 等 |
| 复杂类型处理 | superjson | 开箱即用,支持多种JS类型 | 增加包大小 |
| 自定义类实例 | 自定义转换器 | 完全控制序列化/反序列化过程 | 需要额外开发工作 |
| 高精度计算 | Decimal 转换器 | 保持计算精度 | 需要 Decimal.js 依赖 |
| 大型数据集优化 | 优化JSON转换器 | 高性能,低内存占用 | 功能有限 |
| 特定路由特殊处理 | 选择性转换器 | 优化特定路由性能 | 配置复杂 |
| 频繁相同数据转换 | 缓存转换器 | 减少重复转换开销 | 可能增加内存使用 |
4.10.2 使用 superjson
4.10.2.1 安装
yarn add superjson
4.10.2.1 添加到 服务端和客户端
- 服务端
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
export const t = initTRPC.create({
transformer: superjson,
});
- 客户端
import { createTRPCClient } from '@trpc/client';
import type { AppRouter } from '~/server/routers/_app';
import superjson from 'superjson';
export const client = createTRPCClient<AppRouter>({
links: [
httpLink({
url: 'http://localhost:3000',
transformer: superjson,
}),
],
});
4.11 元数据 meta
import { initTRPC } from '@trpc/server';
// [...]
interface Meta {
authRequired: boolean;
}
export const t = initTRPC.context<Context>().meta<Meta>().create();
export const authedProcedure = t.procedure.use(async (opts) => {
const { meta, next, ctx } = opts;
// only check authorization if enabled
if (meta?.authRequired && !ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next();
});
export const appRouter = t.router({
hello: authedProcedure.meta({ authRequired: false }).query(() => {
return {
greeting: 'hello world',
};
}),
protectedHello: authedProcedure.meta({ authRequired: true }).query(() => {
return {
greeting: 'hello-world',
};
}),
});
5. 客户端用法
5.1 设置
5.1.1 安装依赖
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query
5.1.2 设置 tRPC 上下文提供程序
import { createTRPCContext } from '@trpc/tanstack-react-query';
// 导入你的 AppRouter
import type { AppRouter } from '../server/router';
export const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext<AppRouter>();
5.1.3设置并连接React Query
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server'; // 导入你的后端类型
// 这是一个类型化的 Hook 集合
export const trpc = createTRPCReact<AppRouter>();
// App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
import { trpc } from './utils/trpc';
import { createTRPCClient } from './utils/trpc'; // 你的普通客户端配置
function MyApp() {
// 建议在组件外部或使用 useRef 来保持 QueryClient 实例稳定,这里为演示方便
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
// ... 你的链接配置 (httpBatchLink, wsLink 等)
links: [
/* ... */
],
})
);
return (
// 提供 TRPC 客户端
<trpc.Provider client={trpcClient} queryClient={queryClient}>
{/* 提供 React Query 的上下文 */}
<QueryClientProvider client={queryClient}>
<YourAppContent />
{/* 可选: 添加开发工具 */}
<ReactQueryDevtools />
</QueryClientProvider>
</trpc.Provider>
);
}
5.1.4 用法
常见的 TanStack React Query 接口 (如 QueryKeys、QueryOptions、tationOptions)
5.1.4.1 QueryOptions 查询数据
import { useQuery } from '@tanstack/react-query';
import { trpc } from '~/utils/trpc'; // 你的类型化客户端
function UserProfile({ userId }: { userId: number }) {
// 1. 从 tRPC 过程生成类型安全的选项
const userQueryOptions = trpc.user.byId.queryOptions({ id: userId }, {
// Any Tanstack React Query options
stateTime: 1000 },);
// 2. 将选项传递给原生 useQuery
const { data: user, isLoading, error } = useQuery(userQueryOptions);
// ... 渲染逻辑
}
5.1.4.2 infiniteQueryOptions - 查询无限数据
5.1.4.3 QueryKeys ### 获取查询密钥并在查询客户端上执行操作
5.1.4.4 queryFilter - 创建查询过滤器
5.1.4.5 mutationOptions - 创建突变选项
5.1.4.6 mutationKey - 获取突变密钥
5.1.4.7 subscriptionOptions - 创建订阅选项
5.1.4.8 访问 tRPC 客户端
5.1.4.3 tationOptions
6. tRPC、REST、gRPC、GraphQL 分别是什么
| 维度 | tRPC | REST | gRPC | GraphQL |
|---|---|---|---|---|
| 类型安全 | ⭐️ 端到端自动同步 | ❌ 需手动实现 | ⭐️ 通过 Protobuf 保证 | ⭐️ Schema 强类型 |
| 数据传输效率 | 中(JSON) | 低(JSON/XML) | ⭐️ 高(二进制) | 中(JSON) |
| 开发体验 | ⭐️ 接近本地函数调用 | 中等(需设计路由) | 低(需生成代码) | 中等(需写查询语句) |
| 适用客户端 | 仅 Web/JS | 所有客户端 | 有限(需 gRPC 运行时) | 所有客户端 |
| 实时能力 | 支持(WebSockets) | 需额外实现 | ⭐️ 原生流式支持 | 需订阅扩展 |
| 缓存机制 | 依赖外部库 | ⭐️ 原生 HTTP 缓存 | 自定义 | 复杂(按查询缓存) |
| 多语言支持 | ❌ 仅 TS/JS | ⭐️ 所有语言 | ⭐️ 主流语言支持 | ⭐️ 多语言实现 |
6.1 REST (Representational State Transfer)
6.1.1 核心概念:
- 基于 资源(Resource) 的
架构风格 - 使用标准 HTTP 方法(GET、POST、PUT、DELETE)
- 无状态通信,每个请求包含完整上下文
6.1.2 工作方式:
GET /api/users → 获取用户列表
POST /api/users → 创建新用户
GET /api/users/{id} → 获取单个用户
PUT /api/users/{id} → 更新用户
6.1.3 工作方式:六大架构约束:
| 约束 | 说明 |
|---|---|
| 1. 客户端-服务器分离 | 前端(UI)与后端(数据存储)分离,独立演化 |
| 2. 无状态(Stateless) | 每个请求必须包含所有必要信息,服务器不保存会话状态 |
| 3. 可缓存(Cacheable) | 响应必须明确声明是否可缓存,减少客户端-服务器交互 |
| 4. 统一接口(Uniform Interface) | 包含以下 4 个子原则 ( 资源标识、资源操作、自描述消息、超媒体驱动 ) |
| 5. 分层系统 | 客户端无需知道直接连接的是最终服务器(可经 CDN/代理等中间层) |
| 6. 按需代码(可选) | 服务器可临时下发代码给客户端扩展功能(如 JavaScript) |
6.1.4 特点:
✅ 通用性强(浏览器/移动端/后端直接支持)
✅ 可缓存性强(利用 HTTP 缓存机制)
❌ 容易过度获取(Over-fetching)或获取不足(Under-fetching)
❌ 版本管理复杂(需维护 v1/v2 路由)
❌ 类型安全依赖手动校验
6.1.5 典型场景:
- 公开 API(如 Twitter API)
- 需要简单 CRUD 操作的应用
- 跨多语言系统的集成
6.1.6 扩展( REST API vs RESTful API ):
| 特性 | REST API | RESTful API |
|---|---|---|
| 定义 | 任何基于 HTTP 的 API | 严格遵循 REST 架构约束 的 API |
| 约束满足 | 可能只满足部分约束(如无状态) | 满足全部 6 大约束(尤其 HATEOAS) |
| 统一接口 | 不一定实现 | 强制实现资源标识/操作/超媒体等 |
| 实际应用 | 常见(占自称 “REST” API 的 80%) | 罕见(需完整实现 HATEOAS) |
💡 简单说:
- 所有 RESTful API 都是 REST API
- 但大多数 REST API 并未完全实现 RESTful 规范(尤其是 HATEOAS)
6.2 gRPC (Google Remote Procedure Call)
6.2.1 核心概念:
- 基于 Protocol Buffers 二进制协议
- 使用 HTTP/2 传输协议
- 支持双向流式通信
6.2..2 工作方式:
// 定义服务 (service.proto)
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
message UserRequest {
string id = 1;
}
message UserResponse {
string name = 1;
int32 age = 2;
}
→ 编译生成客户端/服务端代码
6.2.3 特点:
✅ 高性能(二进制编码 + HTTP/2 多路复用)
✅ 强类型约束(通过 Protobuf 定义)
✅ 支持流式传输(实时数据场景)
❌ 浏览器支持有限(需 gRPC-Web 网关)
❌ 调试复杂(二进制数据不易阅读)
6.2.4 典型场景:
- 微服务间通信
- 实时视频/音频传输
- 高性能内部系统(如金融交易)
6.3 GraphQL
6.3.1 核心概念:
- 客户端精确指定所需数据
- 单一端点处理所有请求
- 强类型 schema 定义
6.3.2 工作方式:
# 客户端请求
query {
user(id: "123") {
name
friends(first: 5) {
name
}
}
}
# 服务端响应
{
"data": {
"user": {
"name": "Alice",
"friends": \[ ... ]
}
}
}
6.3.3 特点:
✅ 避免过度获取/获取不足
✅ 强类型 + 自描述 schema
✅ 数据聚合能力强
❌ 缓存实现复杂
❌ 学习曲线陡峭(需掌握 GraphQL 语法)
❌ N+1 查询问题需额外解决(DataLoader)
6.3.4 典型场景:
- 复杂数据关系的应用(社交网络)
- 多客户端适配(Web/移动端共享 API)
- 聚合多数据源的 BFF 层
6.4 tRPC (TypeScript RPC)
6.4.1 核心概念:
- 共享 TypeScript 类型 实现端到端安全
- 无 schema 定义(直接使用 TS 类型)
- 类似本地函数调用体验
6.4.2 工作方式:
// 服务端定义
const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(({ input }) => db.users.find(input.id))
});
// 客户端调用
const user = await trpc.getUser.query({ id: "123" });
// ↑ 输入输出类型自动推断
6.4.3 特点:
✅ 零配置类型安全(编译时+运行时)
✅ 开发体验极佳(无代码生成/无手动序列化)
✅ 轻量级(无额外传输协议)
❌ 仅限 TypeScript/JavaScript 生态
❌ 不适合公开 API(类型强耦合)
❌ 无内置缓存机制(依赖 React Query 等)
6.4.4 典型场景:
- 全栈 TypeScript 应用(Next.js/Express + React)
- 内部工具/后台系统
- 需要快速迭代的类型敏感项目
6.5 如何选择?
- 选 tRPC 当:全栈 TS 项目 + 追求开发速度 + 需要强类型安全
- 选 REST 当:需要简单通用接口 + 支持多语言客户端
- 选 gRPC 当:高性能微服务通信 + 流式数据传输
- 选 GraphQL 当:复杂数据需求 + 多客户端定制响应
7 使用适配器托管tRPC
7.1 Express
7.1.1 安装依赖
yarn add @trpc/server zod
7.1.2 使用 Express 适配器 createExpressMiddleware
7.1.3 createExpressMiddleware 参数
interface ExpressMiddlewareOptions<TRouter extends AnyRouter> {
/**
* tRPC 路由器实例
*/
router: TRouter;
/**
* 创建上下文的函数
*/
createContext: (opts: CreateExpressContextOptions) => Promise<Context> | Context;
/**
* 错误处理回调
*/
onError?: (opts: {
error: TRPCError;
type: 'query' | 'mutation' | 'subscription' | 'unknown';
path: string | undefined;
req: express.Request;
input: unknown;
ctx: Context | undefined;
}) => void;
/**
* 批处理请求配置
*/
batching?: {
enabled: boolean;
};
/**
* 最大请求体大小
*/
maxBodySize?: number;
/**
* 响应时间记录器
*/
responseMeta?: (opts: {
ctx: Context | undefined;
paths: string[] | undefined;
type: 'query' | 'mutation' | 'subscription';
errors: TRPCError[];
data: unknown[] | undefined;
}) => ResponseMetaOptions;
/**
* 服务关闭时的清理函数
*/
teardown?: () => Promise<void>;
/**
* 是否解析请求体
*/
parseBody?: boolean;
/**
* 自定义请求解析器
*/
reqParser?: (req: express.Request) => {
method: string;
query: ParsedUrlQuery;
headers: IncomingHttpHeaders;
body: unknown;
};
}
7.1.4 示例:
import express from 'express';
import cors from 'cors';
import { initTRPC } from '@trpc/server';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { z } from 'zod';
// 1. 初始化 tRPC
const t = initTRPC.context<{ userId?: string }>().create();
// 2. 定义路由
const appRouter = t.router({
// 问候接口
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return { message: `Hello, ${input.name}!` };
}),
// 计算接口
add: t.procedure
.input(z.object({ a: z.number(), b: z.number() }))
.mutation(({ input }) => {
return { result: input.a + input.b };
})
});
// 3. 导出路由类型(供客户端使用)
export type AppRouter = typeof appRouter;
// 4. 创建 Express 应用并配置 tRPC 中间件
const app = express();
app.use(cors()); // 允许跨域请求
// 5. 将 tRPC 挂载到 /trpc 路径
app.use(
'/trpc',
createExpressMiddleware({
router: appRouter,
createContext: ({ req, res }) => createContext({ req, res }),
onError: ({ error, req }) => {
console.error(`tRPC 错误 (${req.originalUrl}):`, error);
if (error.code === 'INTERNAL_SERVER_ERROR') {
sendErrorReport(error);
}
},
batching: {
enabled: true
},
responseMeta: ({ ctx, errors }) => {
// 认证失败返回 401
if (errors.some(e => e.code === 'UNAUTHORIZED')) {
return { status: 401 };
}
// 限制请求返回 429
if (errors.some(e => e.code === 'TOO_MANY_REQUESTS')) {
return { status: 429 };
}
return {};
},
maxBodySize: 10 * 1024 * 1024, // 10MB
teardown: async () => {
console.log('关闭 tRPC 服务...');
await closeDatabaseConnections();
},
parseBody: false, // 使用 body-parser 替代
reqParser: (req) => ({
method: req.method,
query: req.query,
headers: req.headers,
body: req.body
})
})
);
// 启动服务器
const PORT = 4000;
app.listen(PORT, () => {
console.log(`Express server running on http://localhost:${PORT}`);
});
7.2 Next.js createNextApiHandler
7.2.1 createNextApiHandler 参数
interface NextApiHandlerOptions<TRouter extends AnyRouter> {
/**
* tRPC 路由器实例
*/
router: TRouter;
/**
* 创建上下文的函数
*/
createContext: (opts: CreateNextContextOptions) => Promise<Context> | Context;
/**
* 错误处理回调
*/
onError?: (opts: {
error: TRPCError;
type: 'query' | 'mutation' | 'subscription' | 'unknown';
path: string | undefined;
req: NextApiRequest;
input: unknown;
ctx: Context | undefined;
}) => void;
/**
* 批处理请求配置
*/
batching?: {
enabled: boolean;
};
/**
* 响应元数据处理
*/
responseMeta?: (opts: {
ctx: Context | undefined;
paths: string[] | undefined;
type: 'query' | 'mutation' | 'subscription';
errors: TRPCError[];
}) => ResponseMetaOptions;
/**
* 允许的 HTTP 方法
*/
allowMethodOverride?: boolean;
/**
* 允许的原始域 (CORS)
*/
allowOrigin?: string | ((origin: string) => boolean);
/**
* 允许的 HTTP 方法 (CORS)
*/
allowMethods?: string[];
/**
* 允许的 HTTP 头 (CORS)
*/
allowHeaders?: string[];
/**
* 是否暴露头部 (CORS)
*/
exposeHeaders?: string[];
/**
* 最大请求体大小
*/
maxBodySize?: number;
/**
* 日志记录配置
*/
logger?: {
log: (message: string) => void;
error: (message: string) => void;
};
}
7.2.2 示例:
// pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers';
import { createContext } from '../../../server/context';
export default createNextApiHandler({
router: appRouter,
createContext,
onError: ({ error, req, path }) => {
console.error(`tRPC 错误 (${req.url}):`, error);
if (error.code === 'INTERNAL_SERVER_ERROR') {
// 发送错误报告
sendErrorReport(error, path);
}
},
batching: {
enabled: true,
},
responseMeta: ({ ctx, paths, type, errors }) => {
// 公共查询缓存 1 分钟
const isPublicQuery = type === 'query' &&
paths?.every(p => p.startsWith('public.')) &&
errors.length === 0;
if (isPublicQuery) {
return {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120'
}
};
}
// 认证失败返回 401
if (errors.some(e => e.code === 'UNAUTHORIZED')) {
return { status: 401 };
}
return {};
},
allowMethodOverride: true,
allowOrigin: process.env.NODE_ENV === 'production'
? 'https://example.com'
: (origin) => /localhost|vercel.app/.test(origin),
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'X-TRPC-Source'],
exposeHeaders: ['X-TRPC-Response-Id'],
maxBodySize: 5 * 1024 * 1024, // 5MB
logger: {
log: (msg) => console.log(`[tRPC] ${msg}`),
error: (msg) => console.error(`[tRPC ERROR] ${msg}`)
}
});
一个简单全栈项目“栗子”