tRPC 端到端类型安全 API 开发指南:从零依赖架构到最小完整实现
tRPC("Move fast and break nothing. End-to-end typesafe APIs made easy.")是一个让你无需 schema 定义、无需代码生成,就能构建和消费完全类型安全 API 的 TypeScript 工具库。本文以官方 README.md 为主线,结合当前仓库(monorepo 版本 11.18.0)的源码与示例,带你完整理解 tRPC 的核心特性、Monorepo 组织结构、最小可运行的服务端/客户端实现,以及客户端 Links 链与服务器适配器的底层机制。读完本文,你将能够独立搭建一个带类型推导的 tRPC 服务,并知道在仓库中向哪里继续深挖。
一、tRPC 是什么:类型只导入,代码零拷贝
README 对 tRPC 的定位一句话概括:"tRPC allows you to easily build & consume fully typesafe APIs without schemas or code generation."——不用写 API schema(如 OpenAPI),不用跑 codegen 脚本,客户端就能获得输入、输出、错误三个维度的完整静态类型与自动补全。
这一能力的关键在于:客户端只从服务端导入类型声明,不导入任何运行时代码。README 中演示动画的图注明确强调:"The client above is not importing any code from the server, only its type declarations." 在仓库的最小示例中,这一模式体现得非常直白:
- 服务端在 examples/minimal/src/server/index.ts 末尾导出
export type AppRouter = typeof appRouter;,它就是一个普通 TypeScript 类型; - 客户端在 examples/minimal/src/client/index.ts 中只写
import type { AppRouter } from '../server/index.js';——import type在编译后即被擦除,浏览器/运行时不会加载任何服务端模块。
正因为类型来自 typeof appRouter,你在编辑器里重命名路由上的任意变量,类型错误会同时出现在前端和后端——这就是 README 特性列表中"Full static typesafety & autocompletion on the client, for inputs, outputs, and errors"的具体含义。
二、核心特性清单及仓库证据
README 列出了 8 项特性,下面逐条给出仓库内的对应证据:
- 经过充分测试、可用于生产:
packages/tests/server/下有约 40 个测试文件(如 batching.test.ts、websockets.test.ts、streaming.test.ts),覆盖批处理、WebSocket、流式、错误格式化等路径;根目录 vitest.config.ts 统一管理测试。 - 完整静态类型安全:客户端类型由
DecoratedProcedureRecord从路由记录递归推导(见下文第四节源码分析)。 - 无需代码生成(Snappy DX):所有包都直接用
tsdown构建(各包package.json中"build": "tsdown"),没有 schema 编译步骤。 - 零依赖、客户端足迹小:可以核实 packages/server/package.json 与 packages/client/package.json——两者都没有
dependencies字段,仅有devDependencies和peerDependencies(typescript >=5.7.2),README"zero deps"的说法与包清单一致。 - 易于加入既有项目(brownfield):每个框架都有独立适配器入口,按需引入即可(见第六节)。
- 自带适配器(React/Next/Express/Fastify 等):
packages/server/src/adapters/下包含express.ts、fastify/、fetch/、next.ts、next-app-dir.ts、aws-lambda/、standalone.ts、ws.ts等,客户端侧另有 React 生态集成包packages/react-query/与packages/tanstack-react-query/、Next.js 集成包packages/next/。 - Subscriptions(订阅)支持:客户端有 httpSubscriptionLink、
wsLink/,服务端有adapters/ws.ts与wsEncoder.ts,示例examples/next-prisma-websockets-starter/提供完整 WebSocket 场景。 - 请求批处理(Request batching):同一时刻发出的多个请求可自动合并为一次 HTTP 请求,实现于
httpBatchStreamLink,并有专门测试 packages/tests/server/batching.test.ts 验证行为。
三、Monorepo 组织:pnpm workspace + Turbo + Lerna
当前仓库是一个 pnpm workspace + Turborepo + Lerna 的 Monorepo,版本由 lerna.json 统一管理("version": "11.18.0","npmClient": "pnpm")。工作区成员定义在 pnpm-workspace.yaml:
packages:
- 'packages/*'
- 'examples/*'
- 'examples/.*/*'
- 'www'
- 'www/og-image'
核心包结构(packages/ 下):
| 包 | 发布名 | 职责 |
|---|---|---|
| packages/server | @trpc/server |
服务端核心:initTRPC、procedure、router、全部 HTTP/Lambda 适配器 |
| packages/client | @trpc/client |
运行时无关的客户端:createTRPCClient 与 Links 链 |
| packages/react-query | React Query 集成 | createTRPCReact 等(经典集成) |
| packages/tanstack-react-query | TanStack 集成 | 新的 TanStack Query 集成(见仓库博客 www/blog/2025-02-17-new-tanstack-react-query-integration.mdx) |
| packages/next | Next.js 集成 | createTRPCNext、SSR prepass |
| packages/openapi | OpenAPI 支持 | 从 tRPC 路由生成 OpenAPI 文档 |
| packages/upgrade | 升级工具 | v10 → v11 代码迁移 |
| packages/tests | — | 跨包集成测试 |
根目录 package.json 声明了开发环境基线:Node ^24.0.0、pnpm ^10.33.1(packageManager: pnpm@10.33.1)、TypeScript ^5.9.2、zod ^4.2.1,常用脚本如 pnpm build(turbo --filter=./packages/* build)、pnpm dev、pnpm test(vitest)、pnpm lint。
四、快速上手:最小 tRPC 服务(examples/minimal)
仓库提供了 examples/minimal 作为"最小可工作示例"(要求 Node 18+,因需全局 fetch;运行 npm i && npm run dev)。下面按其完整源码拆解服务端、共享层、客户端三层。
4.1 服务端:初始化 tRPC 实例
examples/minimal/src/server/trpc.ts:
import { initTRPC } from '@trpc/server';
import { transformer } from '../shared/transformer.js';
/**
* Initialization of tRPC backend
* Should be done only once per backend!
*/
const t = initTRPC.create({
transformer,
});
/**
* Export reusable router and procedure helpers
* that can be used throughout the router
*/
export const router = t.router;
export const publicProcedure = t.procedure;
要点:initTRPC.create() 每个后端进程只应调用一次;它产出的 t.router / t.procedure 是后续所有路由的构建块。这里的 transformer 来自共享层,用于处理 Date、Temporal、Decimal.js 等 JSON 无法直接表达的类型。
4.2 共享层:数据转换器
examples/minimal/src/shared/transformer.ts:
import superjson from 'superjson';
export const transformer = superjson;
注意注释中的约定:服务端和客户端都应导入这个共享文件,而不是各自直接导入 superjson,以保证两端序列化规则一致。示例使用 superjson 是可选约定,不传 transformer 时走原生 JSON。
4.3 路由定义:query / mutation / 可迭代输出
examples/minimal/src/server/index.ts(假数据库见 examples/minimal/src/server/db.ts):
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { z } from 'zod';
import { db } from './db.js';
import { publicProcedure, router } from './trpc.js';
const appRouter = router({
user: {
list: publicProcedure.query(async () => {
const users = await db.user.findMany();
return users;
}),
byId: publicProcedure.input(z.string()).query(async (opts) => {
const { input } = opts;
const user = await db.user.findById(input);
return user;
}),
create: publicProcedure
.input(z.object({ name: z.string() }))
.mutation(async (opts) => {
const { input } = opts;
const user = await db.user.create(input);
return user;
}),
},
examples: {
iterable: publicProcedure.query(async function* () {
for (let i = 0; i < 3; i++) {
await new Promise((resolve) => setTimeout(resolve, 500));
yield i;
}
}),
},
});
// Export type router type signature, this is used by the client.
export type AppRouter = typeof appRouter;
const server = createHTTPServer({
router: appRouter,
});
server.listen(3000);
这段代码覆盖了 tRPC 服务端的三大要素:
- 命名空间式路由:
user.list、user.byId、user.create由嵌套对象结构自然形成,客户端调用路径与之逐字对应; - zod 输入校验:
.input(z.string())/.input(z.object({ name: z.string() }))同时承担运行时校验与类型推导两职——opts.input被推断为具体类型,create的入参被推断为{ name: string }; - 生成器即流式输出:
examples.iterable用async function*直接 yield 数据,客户端可for await消费,展示了 tRPC 对流式/迭代器输出的原生支持; - 适配器接入:
createHTTPServer来自子路径导出@trpc/server/adapters/standalone,两行代码即得到一个监听 3000 端口的 HTTP 服务。
4.4 客户端:只导类型 + Links 路由
examples/minimal/src/client/index.ts:
import {
createTRPCClient,
httpBatchStreamLink,
httpSubscriptionLink,
splitLink,
} from '@trpc/client';
/**
* We only import the `AppRouter` type from the server - this is not available at runtime
*/
import type { AppRouter } from '../server/index.js';
import { transformer } from '../shared/transformer.js';
const trpc = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000',
transformer,
}),
false: httpBatchStreamLink({
url: 'http://localhost:3000',
transformer,
}),
}),
],
});
async function main() {
const users = await trpc.user.list.query();
console.log('Users:', users);
const createdUser = await trpc.user.create.mutate({ name: 'sachinraja' });
console.log('Created user:', createdUser);
const user = await trpc.user.byId.query('1');
console.log('User 1:', user);
const iterable = await trpc.examples.iterable.query();
for await (const i of iterable) {
console.log('Iterable:', i);
}
}
void main();
源码注释中给出了三条"体验类型安全"的提示:悬停查看推导出的类型、Cmd/Ctrl+点击跳转到定义、重命名后前后端同步报错。注意方法名的映射规则:query 类用 .query(),mutation 类用 .mutate(),subscription 类用 .subscribe(),与路由定义时 t.procedure.query/mutation/subscription 一一对应。
五、客户端架构:Proxy + Links 链的源码解读
README 强调"无运行时膨胀",其秘密在于客户端极薄。阅读 packages/client/src/createTRPCClient.ts 可以看到完整机制:
- 类型推导:
TRPCClient<TRouter>类型通过DecoratedProcedureRecord递归遍历路由记录;其中DecorateProcedure(createTRPCClient.ts#L78-L93)按 procedure 的type字段决定挂哪个方法——query → query、mutation → mutate、subscription → subscribe,入参用inferProcedureInput、返回值用inferTransformedProcedureOutput推导。 - 运行时实现:
createTRPCClient(createTRPCClient.ts#L160-L166)内部先构造TRPCUntypedClient,再包一层createRecursiveProxy(createTRPCClient.ts#L141-L158)。代理截取访问路径['user','byId','query'],把最后一段映射为 procedure 类型,前段拼成user.byId作为 RPC 路径,最终调用client.query('user.byId', ...)。也就是说,trpc.user.byId.query('1')在运行时只是一次路径拼接 + 方法分派,没有反射、没有 schema 查找表。 - Links 链是请求管道:
links目录(packages/client/src/links)包含httpBatchLink.ts、httpBatchStreamLink.ts、httpLink.ts、httpSubscriptionLink.ts、splitLink.ts、retryLink.ts、loggerLink.ts、localLink.ts、wsLink/。示例中的splitLink按操作类型分流:订阅走httpSubscriptionLink(SSE 长连接),其余走httpBatchStreamLink(批处理 + 流式响应)。这也印证了 README 特性中"批处理"与"订阅"两项并非营销话术,而是有独立 Link 实现与测试支撑。 - 包边界:packages/client/src/index.ts 导出
createTRPCClient、TRPCClientError、全部 links,并把旧 APIcreateTRPCProxyClient/inferRouterProxyClient标记为@deprecated(v12 移除)——从 v11 起官方客户端不再带"proxy"字样,语义上强调其并非对服务端的运行时代理,而只是类型镜像。
另外注意 packages/client/package.json 的 peerDependencies 中锁定了同版本的 @trpc/server(11.18.0):客户端的类型推导依赖服务端的类型定义,两者必须版本配套。
六、服务端:子路径导出与适配器矩阵
@trpc/server 的 exports 字段(见 packages/server/package.json)定义了清晰的模块化边界,按需引入:
| 子路径 | 用途 | 示例场景 |
|---|---|---|
@trpc/server/adapters/standalone |
原生 Node HTTP 服务 | 本指南最小示例 |
@trpc/server/adapters/express / fastify / fetch / node-http |
框架集成 | examples/express-minimal/、examples/fastify-server/ |
@trpc/server/adapters/next / next-app-dir |
Next.js Pages Router / App Router API Route | examples/next-minimal-starter/、examples/next-prisma-starter/ |
@trpc/server/adapters/aws-lambda |
AWS Lambda + API Gateway | examples/lambda-api-gateway/、examples/lambda-url/ |
@trpc/server/adapters/ws |
WebSocket 订阅 | examples/next-prisma-websockets-starter/ |
@trpc/server/observable |
自管订阅/流(Observable 原语) | 自定义 Link 开发 |
@trpc/server/rpc、/shared |
共享的 RPC 载荷与错误类型 | 协议层分析 |
入口文件 packages/server/src/index.ts 只有 export * from './@trpc/server',核心实现(initTRPC.ts、procedure.ts、router.ts、middleware.ts 等)位于 packages/server/src/unstable-core-do-not-import/ 目录——目录名本身是明确的维护契约:外部项目不应直接引用该内部模块,只应走公开 API。
七、快速启动:用官方示例脚手架建项目
README 提供了基于 Next.js 全栈示例(examples/next-prisma-starter,内置 Prisma、vitest、Playwright)的一键脚手架命令,覆盖五种包管理器:
# yarn
yarn create next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
# npm
npx create-next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
# pnpm
pnpm create next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
# bun
bunx create-next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
# deno
deno init --npm next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
除了 Next.js,examples/ 目录还按场景组织了大量可运行示例,选型时可对照:
- 纯后端:
express-minimal、fastify-server、standalone-server、bun、deno-deploy、cloudflare-workers; - 服务端架构:
soa(多服务 + 网关)、lazy-load(路由懒加载)、openapi-codegen(OpenAPI 生成与校验); - Next.js 系列:
next-minimal-starter、next-edge-runtime、next-formdata、next-sse-chat(SSE 聊天)、next-websockets-encoder、next-prisma-todomvc、next-prisma-websockets-starter; - 前端最小集:
minimal(本指南所用)、minimal-react(React + Vite)、minimal-content-types(非 JSON 内容类型); - 服务端less:
lambda-api-gateway、lambda-api-gateway-streaming、lambda-url、vercel-edge-runtime。
八、面向 AI Agent 的开发支持
README 单独列出了 AI Agents 章节:若使用 Claude Code、Cursor、Windsurf 等 AI 编码代理,可安装 tRPC 官方 skills 以获得更贴合库约定的代码生成:
npx @tanstack/intent@latest install
这一机制在仓库中可找到落地证据:根目录 package.json 依赖了 @tanstack/intent,且 @trpc/server、@trpc/client 等包的 bin 字段都暴露了 intent 可执行文件,各包 package.json 的 files 字段中包含 skills 目录——例如 packages/server/skills 下有 server-setup、trpc-router、middlewares、validators、subscriptions、adapter-aws-lambda 等主题化的技能说明,packages/client/skills 下则有 client-setup、links、superjson。安装后 AI 代理在生成 tRPC 代码时会参考这些技能文档,减少"凭印象写 tRPC"的偏差。
九、深入阅读路径
沿着本文脉络,推荐按以下顺序在仓库内继续深挖:
- 协议层:packages/server/src/rpc.ts 与
packages/server/src/http.ts——理解 tRPC 的 JSON 载荷格式(?input=编码、[ok, data]响应结构); - 服务端核心:packages/server/src/unstable-core-do-not-import/initTRPC.ts 与
procedureBuilder.ts——procedure 构建链(input → use 中间件 → query/mutation)如何组装; - 行为测试:packages/tests/server/ 中
input.test.ts(zod 校验失败路径)、errorFormatting.test.ts、websockets.memory.test.ts(内存 WebSocket 全链路); - 迁移指南:官方 v10 → v11 迁移文档 www/docs/migration/migrate-from-v10-to-v11.mdx 与配套的
packages/upgrade自动迁移包; - 概念文档:www/docs/further/rpc.md、www/docs/main/concepts.mdx。
适用前提小结:本文基于当前仓库 11.18.0 版本源码;monorepo 本地开发要求 Node 24 + pnpm 10.33,而独立示例(如 examples/minimal)只需 Node 18+;@trpc/client 与 @trpc/server 需同版本使用(peerDependency 约束),TypeScript 要求 >=5.7.2。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00