快速读懂tRPC

204 阅读13分钟

1. 概念 & 快速开始

RPC 是 "远程过程调用" 的缩写。它是一种从另一台计算机(客户端)调用一台计算机(服务器)上的函数的方法。使用传统的 HTTP/REST API,你可以调用 URL 并获得响应。使用 RPC,你可以调用函数并获得响应。

2. 常用术语

核心术语描述
ProcedureAPI 端点可以 querymutationsubscription
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 接口 (如 QueryKeysQueryOptionstationOptions

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 分别是什么

维度tRPCRESTgRPCGraphQL
类型安全⭐️ 端到端自动同步❌ 需手动实现⭐️ 通过 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 APIRESTful 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}`)
  }
});

一个简单全栈项目“栗子”