tRPC 端到端类型安全:无需 API 文档

FreeGuideOnline 最新 2026-06-15

tRPC 端到端类型安全:无需 API 文档

欢迎来到这个免费在线教程,你将学会如何使用 tRPC 构建从数据库到 UI 的完整类型安全应用,真正告别手写 API 文档和手工类型声明。

什么是 tRPC?

tRPC (TypeScript Remote Procedure Call) 是一个全栈 TypeScript 框架,让你能定义一个过程(procedure)并直接在客户端调用它,就像调用本地函数一样。它的核心承诺:如果你修改了服务端代码,客户端不匹配的地方会立刻报 TypeScript 编译错误

比较熟悉的场景:

  • 传统 REST API:你需要手动保持服务端路由、参数、返回值的类型和客户端请求代码的一致性,出问题只在运行时暴露。
  • tRPC:编译器帮你检查一切,无需任何代码生成,完全依靠 TypeScript 的类型推断。

为什么“端到端类型安全”如此重要?

传统开发中的“类型断层”

一个典型 Web 应用通常有这几个层次:

数据库 ↔ 后端逻辑 ↔ API 路由 ↔ HTTP ↔ 客户端请求 ↔ UI 组件

多数方案只能保证某一层的类型安全,比如 Prisma 保证数据库操作的类型,REST + axios 则必须手动声明接口响应类型。任何一处的改动都可能造成运行时空错误,而编译阶段完全不知道。

tRPC 如何消除断层

tRPC 让你在客户端直接导入服务端的类型定义——不是重新编写,而是自动推导出类型。架构变成:

服务端 Procedures → 导出 AppRouter 类型 → 客户端通过 infer 生成强类型 hooks

这样就形成了一条没有缝隙的类型链:数据库模型类型 → 过程输入/输出类型 → 客户端调用类型 → UI props 类型。修改任何一环,整个链条的编译都会立刻给出错误提示。

核心概念快速预览

它们是什么?

  • Router(路由):组织 procedures 的容器,类似于传统 API 的域名空间。
  • Procedure(过程):一个可以被远程调用的函数,有 query(读)、mutation(写)、subscription(实时)三种。
  • Context(上下文):在每个请求中注入数据库连接、用户认证等对象。
  • Middleware(中间件):在过程执行前后运行的逻辑,用于鉴权、日志等。

与 REST 和 GraphQL 的思维差异

REST GraphQL tRPC
接口定义 URL + HTTP 方法 + 状态码 Schema 语言 TypeScript 函数签名
类型来源 手动编写或生成 从 Schema 生成 直接从代码推导
工具需求 Swagger/Postman 文档 GraphiQL/Playground 仅依靠 IDE 自动补全
耦合度 松耦合,但易产生类型不一致 强类型但需单独维护 Schema 强耦合,类型即时同步

从零搭建的第一个 tRPC 应用

我们将创建一个简单的“待办事项”应用,后端使用纯 Node.js + Express,前端使用 React。

1. 项目初始化

mkdir trpc-todo-app
cd trpc-todo-app
# 后端目录
mkdir server && cd server
npm init -y
npm install @trpc/server @trpc/client zod express cors
npm install -D typescript @types/express ts-node nodemon
npx tsc --init
cd ..

# 前端目录
npx create-react-app client --template typescript
cd client
npm install @trpc/client @trpc/server @trpc/react-query @tanstack/react-query zod
cd ..

server/tsconfig.json 启用严格模式和路径映射,便于类型导出。

2. 定义服务端过程

server/src 下创建 trpc.tsrouter.tsindex.ts

trpc.ts – 初始化 tRPC 实例

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

// 上下文类型(稍后注入)
const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;

router.ts – 定义待办事项路由

import { router, publicProcedure } from './trpc';
import { z } from 'zod';

type Todo = {
  id: string;
  content: string;
  done: boolean;
};

const todos: Todo[] = [];

export const appRouter = router({
  list: publicProcedure.query(() => {
    return todos;
  }),
  add: publicProcedure
    .input(z.object({ content: z.string() }))
    .mutation(({ input }) => {
      const todo: Todo = {
        id: `${Date.now()}`,
        content: input.content,
        done: false,
      };
      todos.push(todo);
      return todo;
    }),
});

// 导出路由器类型,这是类型安全的秘密!
export type AppRouter = typeof appRouter;

关键点export type AppRouter = typeof appRouter; 这一行就是客户端获取全部类型信息的源头。你完全不需要额外编写接口文档。

3. 构建 Express 服务器并启用 tRPC 中间件

index.ts

import express from 'express';
import cors from 'cors';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { appRouter } from './router';

const app = express();
app.use(cors());

app.use(
  '/trpc',
  createExpressMiddleware({
    router: appRouter,
    createContext: () => ({}),
  })
);

app.listen(4000, () => {
  console.log('Server running on port 4000');
});

运行服务端:

npx ts-node src/index.ts

4. 在前端安全地消费 API

在 React 项目中创建一个 tRPC 客户端工厂 client/src/trpc.ts

import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../../server/src/router'; // 直接引入类型

export const trpc = createTRPCReact<AppRouter>();

设置 Provider 和配置 client/src/App.tsx

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { trpc } from './trpc';
import { useState } from 'react';
import TodoList from './TodoList';

const queryClient = new QueryClient();

function App() {
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: 'http://localhost:4000/trpc',
        }),
      ],
    })
  );

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <TodoList />
      </QueryClientProvider>
    </trpc.Provider>
  );
}

5. 使用完全类型化的 Hooks 构建 UI

TodoList.tsx 中:

import { trpc } from './trpc';
import { useState } from 'react';

export default function TodoList() {
  const utils = trpc.useContext();
  const listQuery = trpc.list.useQuery();
  const addMutation = trpc.add.useMutation({
    onSuccess: () => {
      utils.list.invalidate(); // 添加成功后刷新列表
    },
  });

  const [text, setText] = useState('');

  return (
    <div>
      <h2>我的待办</h2>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button
        onClick={() => {
          addMutation.mutate({ content: text });
          setText('');
        }}
      >
        添加
      </button>

      {listQuery.data?.map((todo) => (
        <div key={todo.id}>
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            {todo.content}
          </span>
        </div>
      ))}
    </div>
  );
}

注意

  • trpc.list.useQuery() 自动返回 { data: Todo[] } 类型,data 下的每个属性都有智能提示。
  • addMutation.mutate 需要的参数类型被 input 定义的 Zod schema 严格约束,传入错误的属性会即时报错。

6. 感受“修改一处,处处检查”

试着将服务端 add procedure 的输入 schema 改为需要 contentpriority

add: publicProcedure
  .input(z.object({ content: z.string(), priority: z.number() }))
  .mutation(...)

保存后,前端 addMutation.mutate({ content: text }) 会立刻提示缺少 priority 属性。这就是无需文档的端到端类型安全。

进阶技巧:类型安全的上下文注入

上例中上下文为空,真实项目需要注入数据库客户端。例如使用 Prisma:

// server/src/context.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export type Context = { prisma: PrismaClient };

// 在 trpc.ts 中传递类型
import { initTRPC } from '@trpc/server';
import type { Context } from './context';
export const t = initTRPC.context<Context>().create();

现在所有过程都能通过 ctx 访问类型安全的数据库操作,而前端对此无感知却始终能获得正确类型。

常见问题解答

Q: tRPC 只适用于全栈 TypeScript 吗?

基本上是的。tRPC 的价值建立在服务端和客户端都能解析同一份类型上。如果后端使用其他语言,可以考虑用 gRPC 搭配类型生成,但成本更高。

Q: 传统 REST 接口还能用吗?

可以,但就失去了端到端类型安全。你可以在 tRPC 之上再暴露 REST 端点,但建议全站迁移到 tRPC。

Q: GraphQL 也有类型,tRPC 有什么不同?

GraphQL 的类型系统(Schema)独立于实现代码,需要额外工具同步。tRPC 的类型自动从实现代码推导,无需 Code Gen,迭代速度更快。

Q: 生产环境部署有什么注意事项?

  • 使用 ts-node 启动仅适合开发,生产应编译成 JS 运行。
  • 用 Next.js、Nuxt 等框架可以直接集成 API 路由,避免单独部署。
  • tRPC 本身无状态,可轻松水平扩展。

总结:从这里走向生产

通过本教程,你掌握了:

  • 定义带 Zod 校验的过程
  • 导出 AppRouter 类型并跨前端使用
  • 用 React Query hooks 驱动 UI,享受自动补全和编译期错误检查

下一步行动:

  1. 克隆官方示例仓库 trpc/examples-next-prisma-starter 体验完整数据库集成。
  2. 学习中间件和订阅功能构建实时应用。
  3. 将你现有项目的一个模块迁移到 tRPC,直观感受类型安全的效率提升。

当你再也无需反复检查接口参数名称和返回格式时,你就真正融入了端到端类型安全的开发节奏——tRPC 将类型变成了你的文档,而且永远不会过期。