快速入门
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 签名的端点
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. 创建路由器实例
首先,让我们初始化 tRPC 后端。最好在单独的文件中执行此操作,并导出可重用的辅助函数,而不是整个 tRPC 对象。
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
,我们将在其中添加过程。最后,我们需要导出路由器的类型,我们将在稍后的客户端使用它。
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. 添加查询过程
使用 publicProcedure.query()
将查询过程添加到路由器。
以下创建了一个名为 userList
的查询过程,它从我们的数据库返回用户列表
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. 使用输入解析器验证过程输入
要实现 userById
过程,我们需要接受来自客户端的输入。tRPC 允许您定义 输入解析器 来验证和解析输入。您可以定义自己的输入解析器,也可以使用您选择的验证库,例如 zod、yup 或 superstruct。
您在 publicProcedure.input()
上定义输入解析器,然后可以在解析器函数中访问它,如下所示
- 原生
- 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
,例如 z.string()
或 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
,例如 yup.string()
或 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
起来。server.tsts
import {wrap } from '@typeschema/valibot';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 '@typeschema/valibot';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
作为我们的验证库。
4. 添加突变过程
与 GraphQL 类似,tRPC 区分查询和突变过程。
过程在服务器上的工作方式在查询和突变之间没有太大区别。方法名称不同,客户端使用此过程的方式也会发生变化 - 但其他一切都是一样的!
让我们通过将其作为路由器对象上的新属性添加来添加 userCreate
突变
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
现在我们已经定义了路由器,我们可以提供它。tRPC 有许多 适配器,因此您可以使用您选择的任何后端框架。为了简单起见,我们将使用 standalone
适配器。
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);
查看完整的后端代码
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);
在客户端使用您的新后端
现在让我们转到客户端代码,并拥抱端到端类型安全性的力量。当我们导入 AppRouter
类型供客户端使用时,我们已经为我们的系统实现了完全的类型安全性,而不会将任何实现细节泄露给客户端。
1. 设置 tRPC 客户端
client/index.tsts
import {createTRPCClient ,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 =createTRPCClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
client/index.tsts
import {createTRPCClient ,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 =createTRPCClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
tRPC 中的链接类似于 GraphQL 中的链接,它们允许我们在数据发送到服务器 之前 控制数据流。在上面的示例中,我们使用 httpBatchLink,它会自动将多个调用批处理到单个 HTTP 请求中。有关链接的更深入使用,请参阅 链接文档。
2. 查询和突变
您现在可以在 trpc
对象上访问您的 API 过程。试试看!
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' });
完整自动完成
您可以打开 Intellisense 在您的前端探索您的 API。您会发现所有过程路由都在等待您,以及用于调用它们的方法。
client/index.tsts
// Full autocompletion on your routestrpc .u ;
client/index.tsts
// Full autocompletion on your routestrpc .u ;
自己试试看!
下一步
我们强烈建议您查看 示例应用程序,以了解如何在您最喜欢的框架中安装 tRPC。
默认情况下,tRPC 会将复杂类型(如 Date
)映射到它们的 JSON 等效项 (Date
的情况下为 string
)。如果您想保留这些类型的完整性,最简单的方法是 使用 superjson 作为数据转换器。
tRPC 包含为 React 项目和 Next.js 设计的更复杂的客户端工具。