tRPC 프로시저 정의
tRPC 프로시저 정의 (Define Procedures)
tRPC에서 클라이언트에 노출되는 함수 단위를 procedure라고 불러요. 이게 어떤 종류가 있고, 어떻게 재사용 가능한 기본 procedure를 만들어 공통 로직을 묶는지 아는 게 tRPC 사용의 핵심이에요.
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.procedure를 publicProcedure로 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> 같은 타입 헬퍼로 파라미터 타입을 선언할 수 있어요. 핸들러(실행 코드)를 라우터 정의에서 분리해 헬퍼 함수로 빼는 데 유용해요.
더 알아보기
- 권한·인증 조합: Authorization
- 미들웨어: Middlewares
- 서브스크립션 전용 문서: Subscriptions