プロシージャの定義
プロシージャとは、クライアントに公開される関数であり、以下のいずれかです。
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;}),});