跳至正文

tRPC

tRPC 是一个基于 TypeScript 的端到端类型安全 RPC 框架。客户端像调用本地函数一样调用服务端过程,类型从服务端代码直接推导到客户端,无需 codegen、无需 Schema 文件

Note

特点

  • 端到端类型安全:服务端修改接口,客户端立即得到类型提示
  • 客户端像调用本地函数一样调用远程过程,不定义 HTTP 方法和路径
  • 同一页面多个 tRPC 请求自动合并为一个 HTTP 请求(batching)
  • 与 TanStack Query 深度集成,开箱即用缓存、乐观更新、无限滚动

Note

局限

  • 仅限 TypeScript:前后端必须都是 TS,无法服务非 TS 客户端
  • 前后端强耦合:类型变更直接传播,适合同一团队,不适合对外 API
  • 生态较小:相比 REST / GraphQL,社区工具、中间件有限
  • 调试不友好:默认 SuperJSON 序列化 + batching 合并响应,DevTools Network 面板基本不可读

Note

适用场景

  • TypeScript 全栈项目、前后端同团队或 monorepo
  • 追求最快开发速度和零配置类型安全
  • 不需要对外暴露 API 给第三方

REST vs tRPC 的调用形态

┏━━━━━━━━ REST API Client ━━━━━┓ ┏━━━━━━━━━━━━ REST API Server ━━━━━━━━━━━━━━┓
┃                              ┃ ┃                                            ┃
┃ axios.get("/api/user")      ━━━━  GET  ━━━▶ /api/user   ━━▶ getUserList     ┃
┃ axios.get("/api/user/1")    ━━━━  GET  ━━━▶ /api/user/1 ━━▶ getUserById     ┃
┃ axios.post("/api/user")     ━━━━  POST ━━━▶ /api/user   ━━▶ createUser      ┃
┃ axios.put("/api/user/1")    ━━━━  PUT  ━━━▶ /api/user/1 ━━▶ updateUserById  ┃
┃ axios.delete("/api/user/1") ━━━ DELETE ━━━▶ /api/user/1 ━━▶ deleteUserById  ┃
┃                              ┃ ┃                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛


┏━━━━━━━━━ tRPC Client ━━━━━━━━┓ ┏━━━━━━━━━━━━━ tRPC Server ━━━━━━━━━━━━━━━━━┓
┃                              ┃ ┃                                            ┃
┃ trpc.getUserList.query()   ━━━━━━━━━━━━▶   getUserList                      ┃
┃ trpc.getUserById.query()   ━━━━━━━━━━━━▶   getUserById                      ┃
┃ trpc.createUser.mutate()   ━━━━━━━━━━━━▶   createUser                       ┃
┃ trpc.updateUserById.mutate()━━━━━━━━━━━▶   updateUserById                   ┃
┃ trpc.deleteUserById.mutate()━━━━━━━━━━━▶   deleteUserById                   ┃
┃                              ┃ ┃                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

基础概念

概念一句话说明详细
Router(路由器)组织和注册所有 API 端点的容器详见
Procedure(过程)单个 API 端点,分为 query(读)和 mutation(写)详见
Context(上下文)每个请求共享的数据(数据库连接、session 等)详见
Middleware(中间件)在 procedure 执行前后插入逻辑(鉴权、日志)详见
Client(客户端对象)客户端调用服务端 procedure 的类型安全代理详见

类型推导原理

tRPC 不需要 codegen、不需要 Schema 文件——类型直接从服务端代码推导到客户端

GraphQL:  Schema (.graphql) → codegen → 生成 TS 类型 → 客户端使用
tRPC:     服务端代码 → TypeScript 编译器直接推导 → 客户端自动获得类型

核心机制:

// 服务端:定义 procedure
const appRouter = router({
  getUser: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return db.user.findUnique({ where: { id: input.id } });
    }),
});

// 只导出类型,不导出运行时代码
export type AppRouter = typeof appRouter;
// 客户端:import 类型
import type { AppRouter } from "../server/router";

const trpc = createTRPCClient<AppRouter>({
  /* ... */
});

// TS 自动推导:
// - trpc.getUser.query({ id: "1" }) 的输入必须是 { id: string }
// - 返回值类型自动是 User | null
// - 服务端改了字段名,客户端立即报类型错误

Note

要点:为什么不需要 codegen

  • 利用 TypeScript 的 typeof + 泛型推导——AppRouter 类型包含了所有 procedure 的输入/输出
  • import type 只在编译时存在,不会打包到客户端 JS
  • 前提:前后端必须在同一个 TypeScript 项目(monorepo),否则类型无法传递

下载安装

% npm install @trpc/server @trpc/client zod

客户端如果用 React + TanStack Query:

% npm install @trpc/react-query @tanstack/react-query

基本使用

基本流程:

  1. 服务端:初始化 t 实例 → 定义 Router 和 Procedure → 导出 AppRouter 类型
  2. 服务端:挂载 HTTP handler(Fetch adapter / Next.js Route Handler / Express middleware 等)
  3. 客户端:用 createTRPCClient<AppRouter>()createTRPCReact<AppRouter>() 创建代理
  4. 客户端:调用 .query() / .mutate(),享受类型推导

最小可跑示例

服务端:

// 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({
  hello: t.procedure
    .input(z.object({ name: z.string() }))
    .query(({ input }) => `Hello ${input.name}`),
});

export type AppRouter = typeof appRouter;

createHTTPServer({ router: appRouter }).listen(3000);

客户端:

// client.ts
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "./server";

const trpc = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: "http://localhost:3000" })],
});

const greeting = await trpc.hello.query({ name: "Alice" });
// greeting 自动推导为 string;传 { name: 1 } 会编译报错

完整 Router / Context / Middleware / React 集成见 Router 与 Procedure

与 GraphQL 对比

完整对比表与判断流程见 GraphQL 总览 — GraphQL vs tRPC

一句话判断原则:前端只有 TS 客户端 + 同团队 → tRPC;否则 GraphQL