快速入门
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
tRPC 融合了 REST 和 GraphQL 的设计理念。如果您不熟悉这两种技术,建议先了解核心概念。
安装
tRPC 拆分为多个独立包,您可以按需安装。请确保在代码库的对应位置安装所需包。本快速入门指南将保持简洁,仅使用原生客户端。如需框架集成指南,请查看 React 使用指南 和 Next.js 使用指南。
- 需要 TypeScript >=5.7.2
- 强烈建议在
tsconfig.json中启用"strict": true,我们不官方支持非严格模式
首先安装 @trpc/server 和 @trpc/client 这两个核心依赖包:
- npm
- yarn
- pnpm
- bun
- deno
npm install @trpc/server @trpc/client
yarn add @trpc/server @trpc/client
pnpm add @trpc/server @trpc/client
bun add @trpc/server @trpc/client
deno add npm:@trpc/server npm:@trpc/client
定义后端路由
接下来我们将逐步构建类型安全的 tRPC API。初始API包含三个端点,其 TypeScript 类型定义如下:
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. 创建路由实例
首先初始化 tRPC 后端。最佳实践是在独立文件中完成此操作,并导出可复用的辅助函数而非整个 tRPC 对象。
server/trpc.tstsimport {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.tstsimport {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.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
2. 添加查询过程
使用 publicProcedure.query() 方法向路由添加查询过程。
以下示例创建名为 userList 的查询过程,返回数据库中的用户列表:
server/index.tstsimport {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.tstsimport {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() 中定义输入解析器,解析结果可通过解析器函数访问:
- Vanilla
- Zod
- Yup
- Valibot
The input parser should be a function that validates and casts the input of this procedure. It should return a strongly typed value when the input is valid or throw an error if the input is invalid.
server/index.tstsconstappRouter =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.tstsconstappRouter =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 ;}),});
The input parser can be any ZodType, e.g. z.string() or z.object().
server.tstsimport {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.tstsimport {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 ;}),});
The input parser can be any YupSchema, e.g. yup.string() or yup.object().
server.tstsimport * 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.tstsimport * 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 ;}),});
The input parser can be any Valibot schema, e.g. v.string() or v.object().
server.tstsimport * asv from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (v .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport * asv from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (v .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
本文档后续内容将统一使用 zod 作为验证库。
4. 添加变更过程
与 GraphQL 类似,tRPC 区分查询(query)和变更(mutation)过程。
查询和变更过程在服务端的实现差异不大:方法名称不同,客户端调用方式不同,但核心机制完全一致。
通过向路由对象添加新属性来创建 userCreate 变更过程:
server.tstsconstappRouter =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.tstsconstappRouter =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.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
See the full backend code
server/db.tststypeUser = {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.tststypeUser = {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.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tstsimport {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.tstsimport {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.tstsimport {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.tstsimport {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 中的链接(Links)类似于 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 设计的更高级客户端工具。
-
与 React 配合使用(客户端)
- React 集成(推荐)->
@trpc/tanstack-react-query - React 集成(经典)->
@trpc/react-query - 若不确定,请使用
Recommended
- React 集成(推荐)->