跳至主要内容
版本:11.x

定义过程

过程是一个暴露给客户端的函数,它可以是以下之一

  • 一个 Query - 用于获取数据,通常不更改任何数据
  • 一个 Mutation - 用于发送数据,通常用于创建/更新/删除目的
  • 一个 Subscription - 您可能不需要它,我们有 专门的文档

tRPC 中的过程是创建后端函数的非常灵活的原语。它们使用不可变的构建器模式,这意味着您可以 创建可重用的基本过程,这些过程在多个过程之间共享功能。

编写过程

您在 tRPC 设置期间创建的 t 对象返回一个初始 t.procedure,所有其他过程都是基于此构建的

ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.context<{ signGuestBook: () => Promise<void> }>().create();
 
export const router = t.router;
export const publicProcedure = t.procedure;
 
const appRouter = router({
// Queries are the best place to fetch data
hello: publicProcedure.query(() => {
return {
message: 'hello world',
};
}),
 
// Mutations are the best place to do things like updating a database
goodbye: publicProcedure.mutation(async (opts) => {
await opts.ctx.signGuestBook();
 
return {
message: 'goodbye!',
};
}),
});
ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.context<{ signGuestBook: () => Promise<void> }>().create();
 
export const router = t.router;
export const publicProcedure = t.procedure;
 
const appRouter = router({
// Queries are the best place to fetch data
hello: publicProcedure.query(() => {
return {
message: 'hello world',
};
}),
 
// Mutations are the best place to do things like updating a database
goodbye: publicProcedure.mutation(async (opts) => {
await opts.ctx.signGuestBook();
 
return {
message: 'goodbye!',
};
}),
});

可重用的“基本过程”

作为一般模式,我们建议您将 t.procedure 重命名并导出为 publicProcedure,这为您创建其他命名过程以用于特定用例并导出它们提供了空间。这种模式称为“基本过程”,是 tRPC 中代码和行为重用的关键模式;每个应用程序都可能需要它。

在下面的代码中,我们使用可重用的基本过程来构建应用程序的常见用例 - 我们正在为已登录用户创建可重用的基本过程 (authedProcedure) 和另一个基本过程,该过程接受 organizationId 并验证用户是否属于该组织。

这是一个简化的示例;在实践中,您可能希望使用 标头上下文中间件元数据 的某种组合来 验证 和授权您的用户。

ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
 
type Organization = {
id: string;
name: string;
};
type Membership = {
role: 'ADMIN' | 'MEMBER';
Organization: Organization;
};
type User = {
id: string;
memberships: Membership[];
};
type Context = {
/**
* User is nullable
*/
user: User | null;
};
 
const t = initTRPC.context<Context>().create();
 
export const publicProcedure = t.procedure;
 
// procedure that asserts that the user is logged in
export const authedProcedure = t.procedure.use(async function isAuthed(opts) {
const { ctx } = opts;
// `ctx.user` is nullable
if (!ctx.user) {
(property) user: User | null
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
 
return opts.next({
ctx: {
// ✅ user value is known to be non-null now
user: ctx.user,
},
});
});
 
// procedure that a user is a member of a specific organization
export const organizationProcedure = authedProcedure
.input(z.object({ organizationId: z.string() }))
.use(function isMemberOfOrganization(opts) {
const membership = opts.ctx.user.memberships.find(
(m) => m.Organization.id === opts.input.organizationId,
);
if (!membership) {
throw new TRPCError({
code: 'FORBIDDEN',
});
}
return opts.next({
ctx: {
Organization: membership.Organization,
},
});
});
 
export const appRouter = t.router({
whoami: authedProcedure.mutation(async (opts) => {
// user is non-nullable here
const { ctx } = opts;
const ctx: { user: User; }
return ctx.user;
}),
addMember: organizationProcedure
.input(
z.object({
email: z.string().email(),
}),
)
.mutation((opts) => {
// ctx contains the non-nullable user & the organization being queried
const { ctx } = opts;
const ctx: { user: User; Organization: Organization; }
 
// input includes the validate email of the user being invited & the validated organizationId
const { input } = opts;
const input: { organizationId: string; email: string; }
 
return '...';
}),
});
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
 
type Organization = {
id: string;
name: string;
};
type Membership = {
role: 'ADMIN' | 'MEMBER';
Organization: Organization;
};
type User = {
id: string;
memberships: Membership[];
};
type Context = {
/**
* User is nullable
*/
user: User | null;
};
 
const t = initTRPC.context<Context>().create();
 
export const publicProcedure = t.procedure;
 
// procedure that asserts that the user is logged in
export const authedProcedure = t.procedure.use(async function isAuthed(opts) {
const { ctx } = opts;
// `ctx.user` is nullable
if (!ctx.user) {
(property) user: User | null
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
 
return opts.next({
ctx: {
// ✅ user value is known to be non-null now
user: ctx.user,
},
});
});
 
// procedure that a user is a member of a specific organization
export const organizationProcedure = authedProcedure
.input(z.object({ organizationId: z.string() }))
.use(function isMemberOfOrganization(opts) {
const membership = opts.ctx.user.memberships.find(
(m) => m.Organization.id === opts.input.organizationId,
);
if (!membership) {
throw new TRPCError({
code: 'FORBIDDEN',
});
}
return opts.next({
ctx: {
Organization: membership.Organization,
},
});
});
 
export const appRouter = t.router({
whoami: authedProcedure.mutation(async (opts) => {
// user is non-nullable here
const { ctx } = opts;
const ctx: { user: User; }
return ctx.user;
}),
addMember: organizationProcedure
.input(
z.object({
email: z.string().email(),
}),
)
.mutation((opts) => {
// ctx contains the non-nullable user & the organization being queried
const { ctx } = opts;
const ctx: { user: User; Organization: Organization; }
 
// input includes the validate email of the user being invited & the validated organizationId
const { input } = opts;
const input: { organizationId: string; email: string; }
 
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;
}),
});