定义过程
过程是一个暴露给客户端的函数,它可以是以下之一
- 一个
Query
- 用于获取数据,通常不更改任何数据 - 一个
Mutation
- 用于发送数据,通常用于创建/更新/删除目的 - 一个
Subscription
- 您可能不需要它,我们有 专门的文档
tRPC 中的过程是创建后端函数的非常灵活的原语。它们使用不可变的构建器模式,这意味着您可以 创建可重用的基本过程,这些过程在多个过程之间共享功能。
编写过程
您在 tRPC 设置期间创建的 t
对象返回一个初始 t.procedure
,所有其他过程都是基于此构建的
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{signGuestBook : () =>Promise <void> }>().create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;constappRouter =router ({// Queries are the best place to fetch datahello :publicProcedure .query (() => {return {message : 'hello world',};}),// Mutations are the best place to do things like updating a databasegoodbye :publicProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{signGuestBook : () =>Promise <void> }>().create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;constappRouter =router ({// Queries are the best place to fetch datahello :publicProcedure .query (() => {return {message : 'hello world',};}),// Mutations are the best place to do things like updating a databasegoodbye :publicProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});
可重用的“基本过程”
作为一般模式,我们建议您将 t.procedure
重命名并导出为 publicProcedure
,这为您创建其他命名过程以用于特定用例并导出它们提供了空间。这种模式称为“基本过程”,是 tRPC 中代码和行为重用的关键模式;每个应用程序都可能需要它。
在下面的代码中,我们使用可重用的基本过程来构建应用程序的常见用例 - 我们正在为已登录用户创建可重用的基本过程 (authedProcedure
) 和另一个基本过程,该过程接受 organizationId
并验证用户是否属于该组织。
ts
import {initTRPC ,TRPCError } from '@trpc/server';import {z } from 'zod';typeOrganization = {id : string;name : string;};typeMembership = {role : 'ADMIN' | 'MEMBER';Organization :Organization ;};typeUser = {id : string;memberships :Membership [];};typeContext = {/*** User is nullable*/user :User | null;};constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;// procedure that asserts that the user is logged inexport constauthedProcedure =t .procedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});// procedure that a user is a member of a specific organizationexport constorganizationProcedure =authedProcedure .input (z .object ({organizationId :z .string () })).use (functionisMemberOfOrganization (opts ) {constmembership =opts .ctx .user .memberships .find ((m ) =>m .Organization .id ===opts .input .organizationId ,);if (!membership ) {throw newTRPCError ({code : 'FORBIDDEN',});}returnopts .next ({ctx : {Organization :membership .Organization ,},});});export constappRouter =t .router ({whoami :authedProcedure .mutation (async (opts ) => {// user is non-nullable hereconst {ctx } =opts ;returnctx .user ;}),addMember :organizationProcedure .input (z .object ({z .string ().}),).mutation ((opts ) => {// ctx contains the non-nullable user & the organization being queriedconst {ctx } =opts ;// input includes the validate email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
ts
import {initTRPC ,TRPCError } from '@trpc/server';import {z } from 'zod';typeOrganization = {id : string;name : string;};typeMembership = {role : 'ADMIN' | 'MEMBER';Organization :Organization ;};typeUser = {id : string;memberships :Membership [];};typeContext = {/*** User is nullable*/user :User | null;};constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;// procedure that asserts that the user is logged inexport constauthedProcedure =t .procedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});// procedure that a user is a member of a specific organizationexport constorganizationProcedure =authedProcedure .input (z .object ({organizationId :z .string () })).use (functionisMemberOfOrganization (opts ) {constmembership =opts .ctx .user .memberships .find ((m ) =>m .Organization .id ===opts .input .organizationId ,);if (!membership ) {throw newTRPCError ({code : 'FORBIDDEN',});}returnopts .next ({ctx : {Organization :membership .Organization ,},});});export constappRouter =t .router ({whoami :authedProcedure .mutation (async (opts ) => {// user is non-nullable hereconst {ctx } =opts ;returnctx .user ;}),addMember :organizationProcedure .input (z .object ({z .string ().}),).mutation ((opts ) => {// ctx contains the non-nullable user & the organization being queriedconst {ctx } =opts ;// input includes the validate email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
推断“基本过程”的选项类型
除了能够 推断过程的输入和输出类型 之外,您还可以使用 inferProcedureBuilderResolverOptions
推断特定过程构建器(或基本过程)的选项类型。
此类型助手对于向函数参数声明类型很有用。例如,将过程的处理程序(主要执行代码)与其在路由器中的定义分开,或者创建适用于多个过程的辅助函数。
ts
async function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>,) {// input and ctx are now correctly typed! const { ctx, input } = opts;return await prisma.user.findMany({where: {membership: { organizationId: ctx.Organization.id, },},});}export const appRouter = t.router({listMembers: organizationProcedure.query(async (opts) => { // use helper function! const members = await getMembersOfOrganization(opts);return members;}),});
ts
async function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>,) {// input and ctx are now correctly typed! const { ctx, input } = opts;return await prisma.user.findMany({where: {membership: { organizationId: ctx.Organization.id, },},});}export const appRouter = t.router({listMembers: organizationProcedure.query(async (opts) => { // use helper function! const members = await getMembersOfOrganization(opts);return members;}),});