快速开始
tRPC 结合了 REST 和 GraphQL 的概念。如果你对其中任何一个都不熟悉,请查看密钥 概念。
¥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 yourtsconfig.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:
- npm
- yarn
- pnpm
- bun
sh
npm install @trpc/server @trpc/client
sh
npm install @trpc/server @trpc/client
sh
yarn add @trpc/server @trpc/client
sh
yarn add @trpc/server @trpc/client
sh
pnpm add @trpc/server @trpc/client
sh
pnpm add @trpc/server @trpc/client
sh
bun add @trpc/server @trpc/client
sh
bun add @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.tsts
import {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tsts
import {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =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.tsts
import {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tsts
import {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
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.tsts
import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
server/index.tsts
import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
3. 使用输入解析器验证过程输入
¥ Using input parser to validate procedure inputs
为了实现 userById
过程,我们需要接受来自客户端的输入。tRPC 允许你定义 输入解析器 来验证和解析输入。你可以定义自己的输入解析器或使用你选择的验证库,例如 zod、yup 或 superstruct。
¥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:
- Vanilla
- Zod
- Yup
- Valibot
server/index.tsts
constappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server/index.tsts
constappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
ZodType
, e.g. z.string()
or z.object()
.server.tsts
import {z } from 'zod';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tsts
import {z } from 'zod';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
YupSchema
, e.g. yup.string()
or yup.object()
.server.tsts
import * asyup from 'yup';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tsts
import * asyup from 'yup';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
wrap
your schema with [TypeSchema](https://typeschema.com).server.tsts
import {wrap } from '@decs/typeschema';import {string } from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (wrap (string ())).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tsts
import {wrap } from '@decs/typeschema';import {string } from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (wrap (string ())).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
在本文档的其余部分中,我们将使用 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.tsts
constappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
server.tsts
constappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
服务 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.tsts
import {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tsts
import {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
See the full backend code
server/db.tsts
typeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/db.tsts
typeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/trpc.tsts
import {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tsts
import {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tsts
import {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tsts
import {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =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.tsts
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.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
client/index.tsts
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.consttrpc =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.tsts
// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
client/index.tsts
// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
全自动补齐
¥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.tsts
// Full autocompletion on your routestrpc .u ;
client/index.tsts
// Full autocompletion on your routestrpc .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.