Skip to main content
Version: 11.x

定义过程

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

¥A procedure is a function which is exposed to the client, it can be one of:

  • 一个 Query - 用于获取数据,一般不改变任何数据

    ¥a Query - used to fetch data, generally does not change any data

  • 一个 Mutation - 用于发送数据,通常用于创建/更新/删除目的

    ¥a Mutation - used to send data, often for create/update/delete purposes

  • 一个 Subscription - 你可能不需要这个,我们有 专用文档

    ¥a Subscription - you might not need this, and we have dedicated documentation

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

¥Procedures in tRPC are very flexible primitives to create backend functions. They use an immutable builder pattern, which means you can create reusable base procedures that share functionality among multiple procedures.

编写过程

¥Writing procedures

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

¥The t object you create during tRPC setup returns an initial t.procedure which all other procedures are built on:

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!',
};
}),
});

可重复使用 "基本过程"

¥Reusable "Base Procedures"

作为一般模式,我们建议你将 t.procedure 重命名并导出为 publicProcedure,这样你就可以为特定用例创建其他命名过程并导出它们。该模式称为 "基本过程",是 tRPC 中代码和行为重用的关键模式;每个应用都可能需要它。

¥As a general pattern we recommend you rename and export t.procedure as publicProcedure, which then makes room for you to create other named procedures for specific use cases and export those too. This pattern is called "base procedures" and is a key pattern for code and behaviour re-use in tRPC; every application is likely to need it.

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

¥In the below code, we're using reusable base procedures to build common use-cases for our app - we're making a reusable base procedures for logged in users (authedProcedure) & another base procedure that takes an organizationId and validates that a user is part of that organization.

这是一个简化的示例;实际上,你可能希望使用 标头上下文中间件元数据 的某种组合来 authenticateauthorize 你的用户。

¥This is a simplified example; in practice you may want to use some combination of Headers, Context, Middleware, and Metadata, to authenticate and authorize your users.

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 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.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 validate email of the user being invited & the validated organizationId
const { input } = opts;
const input: { organizationId: string; email: string; }
 
return '...';
}),
});

推断 "基本程序" 的选项类型

¥Inferring the options type of a "Base Procedure"

除了能够对过程进行 推断输入和输出类型 操作之外,你还可以使用 inferProcedureBuilderResolverOptions 推断特定过程构建器(或基本过程)的选项类型。

¥In addition to being able to infer the input and output types of a procedure, you can also infer the options type of a specific procedure builder (or base procedure) using inferProcedureBuilderResolverOptions.

此类型助手可用于将类型声明为函数的参数。例如,将过程的处理程序(主要执行代码)与其在路由上的定义分开,或者创建可与多个过程一起使用的辅助函数。

¥This type helper is useful for declaring a type to a function's parameters. Like for example, separating the procedure's handler (main execution code) from its definition at the router, or for creating a helper function that works with multiple procedures.

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;
}),
});

订阅

¥Subscriptions

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

¥For information on subscriptions, see our subscriptions guide.