tRPC 结合了 RESTGraphQL 的概念。如果您对两者都不熟悉,请查看关键的概念

安装

tRPC 分布在几个包中,因此您可以只安装您需要的包。请确保在代码库的正确部分安装您想要的包。对于这个快速入门指南,我们将保持简单,只使用普通客户端。对于框架指南,请查看 与 React 的使用与 Next.js 的使用

要求

  • tRPC 需要 TypeScript >= 4.7.0
  • 我们强烈建议您在 tsconfig.json 中使用 "strict": true,因为我们不正式支持非严格模式。

首先,安装 @trpc/server@trpc/client 包:

  • npm
  • yarn
  • pnpm
  • bun
  1. npm install @trpc/server@next @trpc/client@next
  1. yarn add @trpc/server@next @trpc/client@next
  1. pnpm add @trpc/server@next @trpc/client@next
  1. bun add @trpc/server@next @trpc/client@next

定义后端路由器

让我们逐步构建一个使用 tRPC 的类型安全 API。首先,这个 API 将包含三个端点,具有以下 TypeScript 签名:

  1. type User = { id: string; name: string; };
  2. userList: () => User[];
  3. userById: (id: string) => User;
  4. userCreate: (data: { name: string }) => User;

1. 创建路由器实例

首先,让我们初始化 tRPC 后端。通常的做法是在单独的文件中进行此操作,并导出可重用的辅助函数,而不是整个 tRPC 对象。

  1. // server/trpc.ts
  2. import { initTRPC } from '@trpc/server';
  3. /**
  4. * tRPC 后端的初始化
  5. * 每个后端只应执行一次!
  6. */
  7. const t = initTRPC.create();
  8. /**
  9. * 导出可重用的路由器和过程助手
  10. * 可以在路由器中使用
  11. */
  12. export const router = t.router;
  13. export const publicProcedure = t.procedure;

接下来,我们将初始化我们的主要路由器实例,通常称为 appRouter,在其中我们将稍后添加过程。最后,我们需要导出路由器的类型,稍后在客户端使用。

  1. // server/index.ts
  2. import { router } from './trpc';
  3. const appRouter = router({
  4. // ...
  5. });
  6. // 导出路由器类型的签名,
  7. // 而不是路由器本身。
  8. export type AppRouter = typeof appRouter;

2. 添加查询过程

使用 publicProcedure.query() 向路由器添加查询过程。

以下创建了一个名为 userList 的查询过程,它从我们的数据库返回用户列表:

  1. // server/index.ts
  2. import { db } from './db';
  3. import { publicProcedure, router } from './trpc';
  4. const appRouter = router({
  5. userList: publicProcedure
  6. .query(async () => {
  7. // 从数据源检索用户,这是一个假想的数据库
  8. const users = await db.user.findMany();
  9. return users;
  10. }),
  11. });

3. 使用输入解析器验证过程输入

要实现 userById 过程,我们需要接受来自客户端的输入。tRPC 允许您定义输入解析器来验证和解析输入。您可以定义自己的输入解析器或使用您选择的验证库,如 zodyupsuperstruct

您在 publicProcedure.input() 上定义输入解析器,然后在解析器函数中可以像下面所示访问:

  1. // server/index.ts
  2. const appRouter = router({
  3. // ...
  4. userById: publicProcedure
  5. .input((val: unknown) => {
  6. // 如果值是字符串类型,返回它。
  7. // 它现在将被推断为字符串。
  8. if (typeof val === 'string') return val;
  9. // 哦哦,看起来那个输入不是字符串。
  10. // 我们将抛出一个错误,而不是运行过程。
  11. throw new Error(`Invalid input: ${typeof val}`);
  12. })
  13. .query(async (opts) => {
  14. const { input } = opts;
  15. const user = await db.user.findById(input);
  16. return user;
  17. }),
  18. });

输入解析器可以是任何 ZodType,例如 z.string()z.object()

  1. // server.ts
  2. import { z } from 'zod';
  3. const appRouter = router({
  4. // ...
  5. userById: publicProcedure
  6. .input(z.string())
  7. .query(async (opts) => {
  8. const { input } = opts;
  9. const user = await db.user.findById(input);
  10. return user;
  11. }),
  12. });

输入解析器可以是任何 YupSchema,例如 yup.string()yup.object()

  1. // server.ts
  2. import * as yup from 'yup';
  3. const appRouter = router({
  4. // ...
  5. userById: publicProcedure
  6. .input(yup.string().required())
  7. .query(async (opts) => {
  8. const { input } = opts;
  9. const user = await db.user.findById(input);
  10. return user;
  11. }),
  12. });

使用 Valibot,您只需要用 TypeSchema 包装您的模式。

  1. // server.ts
  2. import { wrap } from '@typeschema/valibot';
  3. import { string } from 'valibot';
  4. const appRouter = router({
  5. // ...
  6. userById: publicProcedure
  7. .input(wrap(string()))
  8. .query(async (opts) => {
  9. const { input } = opts;
  10. const user = await db.user.findById(input);
  11. return user;
  12. }),
  13. });

信息

在本文档的剩余部分中,我们将使用 zod 作为我们的验证库。

4. 添加变异过程

类似于 GraphQL,tRPC 在查询和变异过程之间做出区分。

过程在服务器上的工作原理在查询和变异之间没有太大变化。方法名称不同,客户端使用此过程的方式也不同,但其他一切都相同!

让我们通过将 userCreate 作为我们路由器对象上的一个新属性来添加一个变异:

  1. // server.ts
  2. const appRouter = router({
  3. // ...
  4. userCreate: publicProcedure
  5. .input(z.object({ name: z.string() }))
  6. .mutation(async (opts) => {
  7. const { input } = opts;
  8. const user = await db.user.create(input);
  9. return user;
  10. }),
  11. });

服务 API

现在我们已经定义了我们的路由器,我们可以提供它。tRPC 有许多适配器,因此您可以选择任何后端框架。为了保持简单,我们将使用 standalone 适配器。

  1. // server/index.ts
  2. import { createHTTPServer } from '@trpc/server/adapters/standalone';
  3. const appRouter = router({
  4. // ...
  5. });
  6. const server = createHTTPServer({
  7. router: appRouter,
  8. });
  9. server.listen(3000);

查看完整的后端代码

  1. // server/db.ts
  2. type User = { id: string; name: string };
  3. // 假想的数据库
  4. const users: User[] = [];
  5. export const db = {
  6. user: {
  7. findMany: async () => users,
  8. findById: async (id: string) => users.find((user) => user.id === id),
  9. create: async (data: { name: string }) => {
  10. const user = { id: String(users.length + 1), ...data };
  11. users.push(user);
  12. return user;
  13. },
  14. },
  15. };
  1. // server/trpc.ts
  2. import { initTRPC } from '@trpc/server';
  3. const t = initTRPC.create();
  4. export const router = t.router;
  5. export const publicProcedure = t.procedure;
  1. // server/index.ts
  2. import { createHTTPServer } from '@trpc/server/adapters/standalone';
  3. import { z } from 'zod';
  4. import { db } from './db';
  5. import { publicProcedure, router } from './trpc';
  6. const appRouter = router({
  7. userList: publicProcedure
  8. ```ts
  9. .query(async () => {
  10. const users = await db.user.findMany();
  11. return users;
  12. }),
  13. userById: publicProcedure
  14. .input(z.string())
  15. .query(async (opts) => {
  16. const { input } = opts;
  17. const user = await db.user.findById(input);
  18. return user;
  19. }),
  20. userCreate: publicProcedure
  21. .input(z.object({ name: z.string() }))
  22. .mutation(async (opts) => {
  23. const { input } = opts;
  24. const user = await db.user.create(input);
  25. return user;
  26. }),
  27. });
  28. export type AppRouter = typeof appRouter;
  29. const server = createHTTPServer({
  30. router: appRouter,
  31. });
  32. server.listen(3000);

在客户端使用您的新后端

现在让我们转到客户端代码,并拥抱端到端类型安全的威力。当我们为客户端使用导入 AppRouter 类型时,我们已经实现了我们系统的完全类型安全,而没有向客户端泄露任何实现细节。

1. 设置 tRPC 客户端

  1. // client/index.ts
  2. import { createTRPCClient, httpBatchLink } from '@trpc/client';
  3. import type { AppRouter } from './server';
  4. // 👆 **仅类型** 导入
  5. // 将 AppRouter 作为泛型传递。👇 这允许 `trpc` 对象知道
  6. // 服务器上有哪些可用的过程以及它们的输入/输出类型。
  7. const trpc = createTRPCClient<AppRouter>({
  8. links: [
  9. httpBatchLink({
  10. url: 'http://localhost:3000',
  11. }),
  12. ],
  13. });

在 tRPC 中的链接类似于 GraphQL 中的链接,它们让我们控制数据流 被发送到服务器之前。在上面的例子中,我们使用了 httpBatchLink,它会自动将多个调用批量到一个 HTTP 请求中。有关链接的更深入使用,请查看 链接文档

2. 查询和变异

您现在可以在 trpc 对象上访问您的 API 过程。试试看!

  1. // Inferred types
  2. const user = await trpc.userById.query('1');
  3. const user: { name: string; id: string; } | undefined
  4. const createdUser = await trpc.userCreate.mutate({ name: 'sachinraja' });
  5. const createdUser: { name: string; id: string; }

完整的自动完成

您可以打开您的 Intellisense 来探索前端上的 API。您将发现所有的程序路由都在等待您,以及调用它们的方法。

  1. // Full autocompletion on your routes
  2. trpc.u;
  3. * userById
  4. * userCreate
  5. * userList

请注意,由于您提供的文本包含了一些用于网页布局的CSS媒体查询代码,这些代码在这里没有实际用途,因此我已将它们省略。如果您需要在特定的编程或技术环境中应用这些格式,请确保遵循相应的指南和最佳实践。