tRPC API 指南
使用 tRPC v11 构建类型安全的 API - 完整指南与示例
tRPC API 指南
ProductReady 使用 tRPC v11 构建类型安全的 API。这意味着您可以获得完整的自动补全、类型安全和运行时验证,从后端到前端 - 无需代码生成!
为什么选择 tRPC? 只需编写一次 API,前端自动获得 TypeScript 类型。无需手动类型定义,无需同步问题。
什么是 tRPC?
tRPC 让您构建的 API 具有以下特性:
- 类型安全 - 前端完全知道后端返回什么
- 处处自动补全 - 编辑器帮您编写代码
- 已验证 - 使用 Zod 在运行时检查输入/输出
- 快速构建 - 无需 REST 样板代码,无需 GraphQL 模式
示例:5 行代码创建 API 端点,在前端使用时具有完整类型!
项目结构
src/
├── server/
│ ├── trpc.ts # tRPC 设置和中间件
│ ├── context/ # 请求上下文(用户、数据库)
│ └── routers/ # API 端点
│ ├── index.ts # 主路由(组合所有路由)
│ ├── agentTasks.ts # 任务 CRUD
│ └── posts.ts # 帖子 CRUD
├── app/
│ └── api/
│ └── trpc/
│ └── [trpc]/route.ts # Next.js API 路由处理器
└── lib/
└── trpc/
└── client.tsx # 前端 tRPC 客户端基本概念
1. 过程(Procedures)
过程就是您的 API 端点。有两种类型:
- Query(查询) - 获取数据(GET)
- Mutation(变更) - 修改数据(POST/PUT/DELETE)
// Query 示例
export const tasksRouter = createTRPCRouter({
// GET /api/trpc/agentTasks.list
list: publicProcedure.query(async ({ ctx }) => {
return ctx.db.select().from(agentTasks);
}),
// POST /api/trpc/agentTasks.create
create: protectedProcedure
.input(z.object({ title: z.string() }))
.mutation(async ({ ctx, input }) => {
return ctx.db.insert(agentTasks).values(input);
}),
});2. 上下文(Context)
上下文是所有过程都可以访问的数据(用户会话、数据库等):
// src/server/context/index.ts
export async function createContext({ req, res }: CreateNextContextOptions) {
const session = await getSession(req, res);
return {
session, // 当前用户
db, // 数据库客户端
};
}3. 中间件(Middleware)
中间件在过程之前运行(身份验证检查、日志记录等):
// src/server/trpc.ts
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
session: ctx.session, // TypeScript 现在知道 session 存在!
},
});
});
// 在过程中使用
export const protectedProcedure = t.procedure.use(isAuthed);创建第一个 API 端点
让我们从头开始构建一个"项目"功能!
创建数据库模式
创建 src/db/schema/projects.ts:
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { users } from './users';
export const projects = pgTable('projects', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
description: text('description'),
ownerId: text('owner_id').references(() => users.id).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});创建 tRPC 路由
创建 src/server/routers/projects.ts:
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '../trpc';
import { projects } from '~/db/schema/projects';
import { eq } from 'drizzle-orm';
export const projectsRouter = createTRPCRouter({
// 列出用户的项目
list: protectedProcedure.query(async ({ ctx }) => {
return ctx.db
.select()
.from(projects)
.where(eq(projects.ownerId, ctx.session.user.id));
}),
// 获取单个项目
get: protectedProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ ctx, input }) => {
const project = await ctx.db
.select()
.from(projects)
.where(eq(projects.id, input.id))
.limit(1);
if (!project[0]) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
return project[0];
}),
// 创建项目
create: protectedProcedure
.input(z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
}))
.mutation(async ({ ctx, input }) => {
const [project] = await ctx.db
.insert(projects)
.values({
...input,
ownerId: ctx.session.user.id,
})
.returning();
return project;
}),
});注册路由
添加到 src/server/routers/index.ts:
import { projectsRouter } from './projects';
export const appRouter = createTRPCRouter({
agentTasks: tasksRouter,
posts: postsRouter,
projects: projectsRouter, // ← 添加这行
});在前端使用
现在在 React 组件中使用,具有完整的类型安全:
'use client';
import { trpc } from '~/lib/trpc/client';
export function ProjectsList() {
// 完整的自动补全!✨
const { data: projects, isLoading } = trpc.projects.list.useQuery();
const createMutation = trpc.projects.create.useMutation({
onSuccess: () => {
// 重新获取项目列表
utils.projects.list.invalidate();
},
});
async function handleCreate() {
await createMutation.mutateAsync({
name: '我的新项目',
description: '使用 ProductReady 构建',
});
}
if (isLoading) return <div>加载中...</div>;
return (
<div>
<button onClick={handleCreate}>创建项目</button>
{projects?.map((project) => (
<div key={project.id}>
<h3>{project.name}</h3>
<p>{project.description}</p>
</div>
))}
</div>
);
}完成! 您现在拥有一个完全类型安全的 CRUD API,无需代码生成。前端自动知道所有类型!
常见模式
分页
export const projectsRouter = createTRPCRouter({
list: protectedProcedure
.input(z.object({
cursor: z.number().default(0),
limit: z.number().min(1).max(100).default(20),
}))
.query(async ({ ctx, input }) => {
const items = await ctx.db
.select()
.from(projects)
.limit(input.limit + 1)
.offset(input.cursor);
let nextCursor: number | undefined;
if (items.length > input.limit) {
items.pop();
nextCursor = input.cursor + input.limit;
}
return {
items,
nextCursor,
};
}),
});前端使用无限滚动:
const { data, fetchNextPage, hasNextPage } =
trpc.projects.list.useInfiniteQuery(
{ limit: 20 },
{ getNextPageParam: (lastPage) => lastPage.nextCursor }
);最佳实践
✅ 推荐做法
- 使用 Zod 进行输入验证 - 运行时安全
- 保持路由小巧 - 每个领域一个路由(用户、项目等)
- 使用
protectedProcedure进行身份验证 - 永远不要信任前端 - 返回类型化对象 - 避免
any类型 - 明确处理错误 - 使用 TRPCError 代码
下一步
- OpenAPI REST API - 使用 oRPC contract 的外部 REST API
- 数据库指南 - 模式设计和查询
- 身份验证 - 保护您的端点
- 测试指南 - 为您的 API 编写测试
资源
- tRPC 文档: trpc.io/docs
- 示例路由:
src/server/routers/ - Zod 验证: zod.dev