프로시저 정의하기
이 페이지는 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를 받아 사용자가 해당 조직에 속해 있는지 검증하는 또 다른 기본 프로시저를 만듭니다.
이는 단순화된 예시입니다. 실제로는 헤더, 컨텍스트, 미들웨어, 메타데이터를 조합하여 사용자를 인증 및 권한 부여하는 것이 좋습니다.
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;}),});
구독(Subscriptions)
구독에 대한 정보는 구독 가이드에서 확인할 수 있습니다.