跳至主内容
版本:11.x

定义过程

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

过程(Procedure)是暴露给客户端的函数,它可以是以下类型之一:

  • 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 并验证用户是否属于该组织的基础过程。

这是简化示例;实际应用中你可能需要组合使用 HeadersContextMiddlewareMetadata验证授权用户。

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.query(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 validated 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.query(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 validated 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;
}),
});

订阅

关于订阅的信息,请参阅我们的订阅指南