tRPC 结合了 REST 和 GraphQL 的概念。如果您对两者都不熟悉,请查看关键的概念。
安装
tRPC 分布在几个包中,因此您可以只安装您需要的包。请确保在代码库的正确部分安装您想要的包。对于这个快速入门指南,我们将保持简单,只使用普通客户端。对于框架指南,请查看 与 React 的使用 和 与 Next.js 的使用。
要求
- tRPC 需要 TypeScript >= 4.7.0
- 我们强烈建议您在
tsconfig.json中使用"strict": true,因为我们不正式支持非严格模式。
首先,安装 @trpc/server 和 @trpc/client 包:
- npm
- yarn
- pnpm
- bun
npm install @trpc/server@next @trpc/client@next
yarn add @trpc/server@next @trpc/client@next
pnpm add @trpc/server@next @trpc/client@next
bun add @trpc/server@next @trpc/client@next
定义后端路由器
让我们逐步构建一个使用 tRPC 的类型安全 API。首先,这个 API 将包含三个端点,具有以下 TypeScript 签名:
type User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. 创建路由器实例
首先,让我们初始化 tRPC 后端。通常的做法是在单独的文件中进行此操作,并导出可重用的辅助函数,而不是整个 tRPC 对象。
// server/trpc.tsimport { initTRPC } from '@trpc/server';/*** tRPC 后端的初始化* 每个后端只应执行一次!*/const t = initTRPC.create();/*** 导出可重用的路由器和过程助手* 可以在路由器中使用*/export const router = t.router;export const publicProcedure = t.procedure;
接下来,我们将初始化我们的主要路由器实例,通常称为 appRouter,在其中我们将稍后添加过程。最后,我们需要导出路由器的类型,稍后在客户端使用。
// server/index.tsimport { router } from './trpc';const appRouter = router({// ...});// 导出路由器类型的签名,// 而不是路由器本身。export type AppRouter = typeof appRouter;
2. 添加查询过程
使用 publicProcedure.query() 向路由器添加查询过程。
以下创建了一个名为 userList 的查询过程,它从我们的数据库返回用户列表:
// server/index.tsimport { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({userList: publicProcedure.query(async () => {// 从数据源检索用户,这是一个假想的数据库const users = await db.user.findMany();return users;}),});
3. 使用输入解析器验证过程输入
要实现 userById 过程,我们需要接受来自客户端的输入。tRPC 允许您定义输入解析器来验证和解析输入。您可以定义自己的输入解析器或使用您选择的验证库,如 zod、yup 或 superstruct。
您在 publicProcedure.input() 上定义输入解析器,然后在解析器函数中可以像下面所示访问:
// server/index.tsconst appRouter = router({// ...userById: publicProcedure.input((val: unknown) => {// 如果值是字符串类型,返回它。// 它现在将被推断为字符串。if (typeof val === 'string') return val;// 哦哦,看起来那个输入不是字符串。// 我们将抛出一个错误,而不是运行过程。throw new Error(`Invalid input: ${typeof val}`);}).query(async (opts) => {const { input } = opts;const user = await db.user.findById(input);return user;}),});
输入解析器可以是任何 ZodType,例如 z.string() 或 z.object()。
// server.tsimport { z } from 'zod';const appRouter = router({// ...userById: publicProcedure.input(z.string()).query(async (opts) => {const { input } = opts;const user = await db.user.findById(input);return user;}),});
输入解析器可以是任何 YupSchema,例如 yup.string() 或 yup.object()。
// server.tsimport * as yup from 'yup';const appRouter = router({// ...userById: publicProcedure.input(yup.string().required()).query(async (opts) => {const { input } = opts;const user = await db.user.findById(input);return user;}),});
使用 Valibot,您只需要用 TypeSchema 包装您的模式。
// server.tsimport { wrap } from '@typeschema/valibot';import { string } from 'valibot';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;const user = await db.user.findById(input);return user;}),});
信息
在本文档的剩余部分中,我们将使用 zod 作为我们的验证库。
4. 添加变异过程
类似于 GraphQL,tRPC 在查询和变异过程之间做出区分。
过程在服务器上的工作原理在查询和变异之间没有太大变化。方法名称不同,客户端使用此过程的方式也不同,但其他一切都相同!
让我们通过将 userCreate 作为我们路由器对象上的一个新属性来添加一个变异:
// server.tsconst appRouter = router({// ...userCreate: publicProcedure.input(z.object({ name: z.string() })).mutation(async (opts) => {const { input } = opts;const user = await db.user.create(input);return user;}),});
服务 API
现在我们已经定义了我们的路由器,我们可以提供它。tRPC 有许多适配器,因此您可以选择任何后端框架。为了保持简单,我们将使用 standalone 适配器。
// server/index.tsimport { createHTTPServer } from '@trpc/server/adapters/standalone';const appRouter = router({// ...});const server = createHTTPServer({router: appRouter,});server.listen(3000);
查看完整的后端代码
// server/db.tstype User = { id: string; name: string };// 假想的数据库const users: User[] = [];export const db = {user: {findMany: async () => users,findById: async (id: string) => users.find((user) => user.id === id),create: async (data: { name: string }) => {const user = { id: String(users.length + 1), ...data };users.push(user);return user;},},};
// server/trpc.tsimport { initTRPC } from '@trpc/server';const t = initTRPC.create();export const router = t.router;export const publicProcedure = t.procedure;
// server/index.tsimport { createHTTPServer } from '@trpc/server/adapters/standalone';import { z } from 'zod';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({userList: publicProcedure```ts.query(async () => {const users = await db.user.findMany();return users;}),userById: publicProcedure.input(z.string()).query(async (opts) => {const { input } = opts;const user = await db.user.findById(input);return user;}),userCreate: publicProcedure.input(z.object({ name: z.string() })).mutation(async (opts) => {const { input } = opts;const user = await db.user.create(input);return user;}),});export type AppRouter = typeof appRouter;const server = createHTTPServer({router: appRouter,});server.listen(3000);
在客户端使用您的新后端
现在让我们转到客户端代码,并拥抱端到端类型安全的威力。当我们为客户端使用导入 AppRouter 类型时,我们已经实现了我们系统的完全类型安全,而没有向客户端泄露任何实现细节。
1. 设置 tRPC 客户端
// client/index.tsimport { createTRPCClient, httpBatchLink } from '@trpc/client';import type { AppRouter } from './server';// 👆 **仅类型** 导入// 将 AppRouter 作为泛型传递。👇 这允许 `trpc` 对象知道// 服务器上有哪些可用的过程以及它们的输入/输出类型。const trpc = createTRPCClient<AppRouter>({links: [httpBatchLink({url: 'http://localhost:3000',}),],});
在 tRPC 中的链接类似于 GraphQL 中的链接,它们让我们控制数据流 在 被发送到服务器之前。在上面的例子中,我们使用了 httpBatchLink,它会自动将多个调用批量到一个 HTTP 请求中。有关链接的更深入使用,请查看 链接文档。
2. 查询和变异
您现在可以在 trpc 对象上访问您的 API 过程。试试看!
// Inferred typesconst user = await trpc.userById.query('1');const user: { name: string; id: string; } | undefinedconst createdUser = await trpc.userCreate.mutate({ name: 'sachinraja' });const createdUser: { name: string; id: string; }
完整的自动完成
您可以打开您的 Intellisense 来探索前端上的 API。您将发现所有的程序路由都在等待您,以及调用它们的方法。
// Full autocompletion on your routestrpc.u;* userById* userCreate* userList
请注意,由于您提供的文本包含了一些用于网页布局的CSS媒体查询代码,这些代码在这里没有实际用途,因此我已将它们省略。如果您需要在特定的编程或技术环境中应用这些格式,请确保遵循相应的指南和最佳实践。
