Skip to main content
Version: 10.x

快速开始

tRPC 结合了 RESTGraphQL 的概念。如果你对其中任何一个都不熟悉,请查看密钥 概念

¥tRPC combines concepts from REST and GraphQL. If you are unfamiliar with either, take a look at the key Concepts.

安装

¥Installation

tRPC 分为多个包,因此你可以只安装你需要的包。确保将所需的软件包安装在代码库的正确部分。对于本快速入门指南,我们将保持简单并仅使用普通客户端。有关框架指南,请查看 与 React 一起使用与 Next.js 一起使用

¥tRPC is split between several packages, so you can install only what you need. Make sure to install the packages you want in the proper sections of your codebase. For this quickstart guide we'll keep it simple and use the vanilla client only. For framework guides, checkout usage with React and usage with Next.js.

要求
  • tRPC 需要 TypeScript >= 4.7.0

    ¥tRPC requires TypeScript >= 4.7.0

  • 我们强烈建议你在 tsconfig.json 中使用 "strict": true,因为我们不正式支持非严格模式。

    ¥We strongly recommend you using "strict": true in your tsconfig.json as we don't officially support non-strict mode.

首先安装 @trpc/server@trpc/client 软件包:

¥Start off by installing the @trpc/server and @trpc/client packages:

sh
npm install @trpc/server @trpc/client
sh
npm install @trpc/server @trpc/client

定义后端路由

¥Defining a backend router

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

¥Let's walk through the steps of building a typesafe API with tRPC. To start, this API will contain three endpoints with these TypeScript signatures:

ts
type User = { id: string; name: string; };
userList: () => User[];
userById: (id: string) => User;
userCreate: (data: { name: string }) => User;
ts
type User = { id: string; name: string; };
userList: () => User[];
userById: (id: string) => User;
userCreate: (data: { name: string }) => User;

1. 创建路由实例

¥ Create a router instance

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

¥First, let's initialize the tRPC backend. It's good convention to do this in a separate file and export reusable helper functions instead of the entire tRPC object.

server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
 
/**
 
* Initialization of tRPC backend
 
* Should be done only once per backend!
*/
const t = initTRPC.create();
 
/**
 
* Export reusable router and procedure helpers
 
* that can be used throughout the router
*/
export const router = t.router;
export const publicProcedure = t.procedure;
server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
 
/**
 
* Initialization of tRPC backend
 
* Should be done only once per backend!
*/
const t = initTRPC.create();
 
/**
 
* Export reusable router and procedure helpers
 
* that can be used throughout the router
*/
export const router = t.router;
export const publicProcedure = t.procedure;

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

¥Next, we'll initialize our main router instance, commonly referred to as appRouter, in which we'll later add procedures to. Lastly, we need to export the type of the router which we'll later use on the client side.

server/index.ts
ts
import { router } from './trpc';
 
const appRouter = router({
// ...
});
 
// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof appRouter;
server/index.ts
ts
import { router } from './trpc';
 
const appRouter = router({
// ...
});
 
// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof appRouter;

2. 添加查询过程

¥ Add a query procedure

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

¥Use publicProcedure.query() to add a query procedure to the router.

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

¥The following creates a query procedure called userList that returns a list of users from our database:

server/index.ts
ts
import { db } from './db';
import { publicProcedure, router } from './trpc';
 
const appRouter = router({
userList: publicProcedure
.query(async () => {
// Retrieve users from a datasource, this is an imaginary database
const users = await db.user.findMany();
const users: User[]
return users;
}),
});
server/index.ts
ts
import { db } from './db';
import { publicProcedure, router } from './trpc';
 
const appRouter = router({
userList: publicProcedure
.query(async () => {
// Retrieve users from a datasource, this is an imaginary database
const users = await db.user.findMany();
const users: User[]
return users;
}),
});

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

¥ Using input parser to validate procedure inputs

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

¥To implement the userById procedure, we need to accept input from the client. tRPC lets you define input parsers to validate and parse the input. You can define your own input parser or use a validation library of your choice, like zod, yup, or superstruct.

你在 publicProcedure.input() 上定义输入解析器,然后可以在解析器函数上访问该解析器,如下所示:

¥You define your input parser on publicProcedure.input(), which can then be accessed on the resolver function as shown below:

The input parser can be any ZodType, e.g. z.string() or z.object().

server.ts
ts
import { z } from 'zod';
 
const appRouter = router({
// ...
userById: publicProcedure
.input(z.string())
.query(async (opts) => {
const { input } = opts;
const input: string
// Retrieve the user with the given ID
const user = await db.user.findById(input);
const user: User | undefined
return user;
}),
});
server.ts
ts
import { z } from 'zod';
 
const appRouter = router({
// ...
userById: publicProcedure
.input(z.string())
.query(async (opts) => {
const { input } = opts;
const input: string
// Retrieve the user with the given ID
const user = await db.user.findById(input);
const user: User | undefined
return user;
}),
});
信息

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

¥Throughout the remaining of this documentation, we will use zod as our validation library.

4. 添加突变过程

¥ Adding a mutation procedure

与 GraphQL 类似,tRPC 区分查询过程和修改过程。

¥Similar to GraphQL, tRPC makes a distinction between query and mutation procedures.

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

¥The way a procedure works on the server doesn't change much between a query and a mutation. The method name is different, and the way that the client will use this procedure changes - but everything else is the same!

让我们通过将 userCreate 突变添加为路由对象上的新属性来添加它:

¥Let's add a userCreate mutation by adding it as a new property on our router object:

server.ts
ts
const appRouter = router({
// ...
userCreate: publicProcedure
.input(z.object({ name: z.string() }))
.mutation(async (opts) => {
const { input } = opts;
const input: { name: string; }
// Create a new user in the database
const user = await db.user.create(input);
const user: { name: string; id: string; }
return user;
}),
});
server.ts
ts
const appRouter = router({
// ...
userCreate: publicProcedure
.input(z.object({ name: z.string() }))
.mutation(async (opts) => {
const { input } = opts;
const input: { name: string; }
// Create a new user in the database
const user = await db.user.create(input);
const user: { name: string; id: string; }
return user;
}),
});

服务 API

¥Serving the API

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

¥Now that we have defined our router, we can serve it. tRPC has many adapters so you can use any backend framework of your choice. To keep it simple, we'll use the standalone adapter.

server/index.ts
ts
import { createHTTPServer } from '@trpc/server/adapters/standalone';
 
const appRouter = router({
// ...
});
 
const server = createHTTPServer({
router: appRouter,
});
 
server.listen(3000);
server/index.ts
ts
import { createHTTPServer } from '@trpc/server/adapters/standalone';
 
const appRouter = router({
// ...
});
 
const server = createHTTPServer({
router: appRouter,
});
 
server.listen(3000);
See the full backend code
server/db.ts
ts
type User = { id: string; name: string };
 
// Imaginary database
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/db.ts
ts
type User = { id: string; name: string };
 
// Imaginary database
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.ts
ts
import { initTRPC } from '@trpc/server';
 
const t = initTRPC.create();
 
export const router = t.router;
export const publicProcedure = t.procedure;
server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
 
const t = initTRPC.create();
 
export const router = t.router;
export const publicProcedure = t.procedure;

server/index.ts
ts
import { createHTTPServer } from "@trpc/server/adapters/standalone";
import { z } from "zod";
import { db } from "./db";
import { publicProcedure, router } from "./trpc";
 
const appRouter = router({
userList: publicProcedure
.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);
server/index.ts
ts
import { createHTTPServer } from "@trpc/server/adapters/standalone";
import { z } from "zod";
import { db } from "./db";
import { publicProcedure, router } from "./trpc";
 
const appRouter = router({
userList: publicProcedure
.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);

在客户端上使用新的后端

¥Using your new backend on the client

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

¥Let's now move to the client-side code and embrace the power of end-to-end typesafety. When we import the AppRouter type for the client to use, we have achieved full typesafety for our system without leaking any implementation details to the client.

1. 设置 tRPC 客户端

¥ Setup the tRPC Client

client/index.ts
ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
// 👆 **type-only** import
 
// Pass AppRouter as generic here. 👇 This lets the `trpc` object know
// what procedures are available on the server and their input/output types.
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
}),
],
});
client/index.ts
ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
// 👆 **type-only** import
 
// Pass AppRouter as generic here. 👇 This lets the `trpc` object know
// what procedures are available on the server and their input/output types.
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
}),
],
});

tRPC 中的链接与 GraphQL 中的链接类似,它们让我们在发送到服务器之前控制数据流。在上面的示例中,我们使用 httpBatchLink,它自动将多个调用批量处理为单个 HTTP 请求。有关链接的更深入使用,请参阅 链接文档

¥Links in tRPC are similar to links in GraphQL, they let us control the data flow before being sent to the server. In the example above, we use the httpBatchLink, which automatically batches up multiple calls into a single HTTP request. For more in-depth usage of links, see the links documentation.

2. 查询和变异

¥ Querying & mutating

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

¥You now have access to your API procedures on the trpc object. Try it out!

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

全自动补齐

¥Full autocompletion

你可以打开 Intellisense 来探索前端的 API。你会发现所有的过程路由以及调用它们的方法都在等待着你。

¥You can open up your Intellisense to explore your API on your frontend. You'll find all of your procedure routes waiting for you along with the methods for calling them.

client/index.ts
ts
// Full autocompletion on your routes
trpc.u;
      
client/index.ts
ts
// Full autocompletion on your routes
trpc.u;
      

自己试试吧!

¥Try it out for yourself!

下一步

¥Next steps

提示

我们强烈建议你查看 示例应用 以了解 tRPC 如何安装在你喜欢的框架中。

¥We highly encourage you to check out the example apps to learn about how tRPC is installed in your favorite framework.

提示

默认情况下,tRPC 会将复杂类型(如 Date)映射到其 JSON 等效项(在 Date 的情况下为 string)。如果你想添加以保留这些类型的完整性,添加对这些类型的支持的最简单方法是将 使用超级 json 作为数据转换器。

¥By default, tRPC will map complex types like Date to their JSON-equivalent (string in the case of Date). If you want to add to retain the integrity of those types, the easiest way to add support for these is to use superjson as a Data Transformer.

tRPC 包括专为 React 项目和 Next.js 设计的更复杂的客户端工具。

¥tRPC includes more sophisticated client-side tooling designed for React projects and Next.js.