プロシージャの定義
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
プロシージャはクライアントに公開される関数であり、以下のいずれかの種類があります:
-
Query- データ取得に使用され、通常はデータを変更しません -
Mutation- データ送信に使用され、作成・更新・削除目的で頻繁に利用されます -
Subscription- 必要性が低い場合があり、専用ドキュメントを用意しています
tRPCにおけるプロシージャは、バックエンド関数を作成するための非常に柔軟なプリミティブです。イミュータブルなビルダーパターンを採用しているため、再利用可能なベースプロシージャを作成して複数のプロシージャ間で機能を共有できます。
プロシージャの作成
tRPCセットアップ時に作成するtオブジェクトは初期t.procedureを返し、これが他のすべてのプロシージャの基盤となります:
tsimport {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!',};}),});
tsimport {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を受け取りユーザーがその組織に所属していることを検証する別のベースプロシージャを作成しています。
これは簡略化した例です。実際にはHeaders、Context、Middleware、Metadataを組み合わせて、ユーザーの認証と認可を行う必要があるでしょう。
tsimport {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 .query (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 validated email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
tsimport {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 .query (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 validated email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
「ベースプロシージャ」のオプション型推論
プロシージャの入力/出力型を推論することに加え、inferProcedureBuilderResolverOptionsを使用して特定のプロシージャビルダー(またはベースプロシージャ)のオプション型を推論できます。
この型ヘルパーは関数パラメータの型宣言に有用です。例:プロシージャのハンドラー(主要実行コード)をルーター定義から分離する場合、または複数プロシージャで動作するヘルパー関数を作成する場合など。
tsasync 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;}),});
tsasync 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;}),});
サブスクリプション
サブスクリプションに関する詳細は、サブスクリプションガイドをご覧ください。