tRPC 프로시저 정의

tRPC 프로시저 정의 (Define Procedures)

tRPC에서 클라이언트에 노출되는 함수 단위를 procedure라고 불러요. 이게 어떤 종류가 있고, 어떻게 재사용 가능한 기본 procedure를 만들어 공통 로직을 묶는지 아는 게 tRPC 사용의 핵심이에요.

출처: https://trpc.io/docs/server/procedures

procedure는 세 종류가 있어요.

  • Query: 데이터를 가져올 때 사용 (일반적으로 데이터를 변경하지 않음)
  • Mutation: 데이터를 보낼 때 사용 (보통 create/update/delete)
  • Subscription: 실시간 데이터 (필요 없으면 안 써도 됨)

기본 형태는 이래요. .query()는 읽기, .mutation()은 쓰기라는 의미를 담아요.

import { initTRPC } from '@trpc/server';

const t = initTRPC.context<{ signGuestBook: () => Promise<void> }>().create();

export const router = t.router;
export const publicProcedure = t.procedure;

const appRouter = router({
  hello: publicProcedure.query(() => ({ message: 'hello world' })),
  goodbye: publicProcedure.mutation(async (opts) => {
    await opts.ctx.signGuestBook();
    return { message: 'goodbye!' };
  }),
});

프로시저는 불변 빌더 패턴을 써요. 그래서 공통 기능을 가진 기본(base) procedure를 만들어 재사용할 수 있어요. 흔한 패턴은 t.procedurepublicProcedure로 export하고, 여기에 .use()(미들웨어)를 쌓아 인증 같은 로직을 가진 파생 procedure를 만드는 거예요.

export const authedProcedure = t.procedure.use(async function isAuthed(opts) {
  const { ctx } = opts;
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return opts.next({ ctx: { user: ctx.user } });
});

.next()로 컨텍스트를 교체하면 그 뒤 하위 단계에서 타입이 좁혀져요. 예를 들어 authedProcedure 안에서는 ctx.user가 "null 아님"으로 추론돼요. 여기에 .input()으로 조직 ID를 검증하는 organizationProcedure처럼 더 구체적인 procedure도 만들 수 있어요. 이런 "base procedures" 패턴이 tRPC에서 코드·동작 재사용의 핵심 기법이에요.

프로시저의 옵션 타입을 별도 함수로 분리해서 쓰고 싶다면 inferProcedureBuilderResolverOptions<typeof organizationProcedure> 같은 타입 헬퍼로 파라미터 타입을 선언할 수 있어요. 핸들러(실행 코드)를 라우터 정의에서 분리해 헬퍼 함수로 빼는 데 유용해요.

더 알아보기