tRPC 라우터 병합

tRPC 라우터 병합 (Merging Routers)

API 코드가 커지면 한 파일에 전부 넣기 어려워져요. tRPC는 라우터를 여러 파일로 쪼갰다가 다시 합치는 방법을 제공해요. 이렇게 하면 사용자·게시물 같은 도메인 단위로 코드를 정리할 수 있어요.

출처: https://trpc.io/docs/server/merging-routers

가장 단순한 방법은 한 라우터 안에 다른 라우터를 네임스페이스로 넣는 거예요.

// routers/user.ts
export const userRouter = router({
  list: publicProcedure.query(() => []),
});

// routers/post.ts
export const postRouter = router({
  create: publicProcedure
    .input(z.object({ title: z.string() }))
    .mutation((opts) => { /* ... */ }),
  list: publicProcedure.query(() => []),
});

// routers/_app.ts
const appRouter = router({
  user: userRouter,
  post: postRouter,
});
export type AppRouter = typeof appRouter;

이러면 클라이언트에서 trpc.user.list처럼 네임스페이스를 앞에 붙여 호출해요. 도메인별로 묶어 관리하고 싶을 때 잘 맞아요.

반대로 모든 procedure를 하나의 평평한 네임스페이스에 두고 싶다면 t.mergeRouters를 써요. 이때는 각 서브라우터의 procedure 이름이 겹치지 않게 직접 관리해야 해요.

export const appRouter = mergeRouters(userRouter, postRouter);

추가로 lazy() 함수를 쓰면 라우터를 동적으로 불러올 수 있어요. 콜드 스타트(cold start)를 줄이는 데 유용해요. lazy 로드한 라우터도 사용법은 일반 라우터와 똑같아요.

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

export const appRouter = router({
  // 모듈에 라우터가 정확히 1개면 단축 표기
  greeting: lazy(() => import('./greeting.js')),
  // 라우터를 여러 개 export할 때는 명시적으로 골라서
  user: lazy(() => import('./user.js').then((m) => m.userRouter)),
});

어느 방식을 쓰든 AppRouter = typeof appRouter 타입을 export하면 클라이언트가 그 구조를 그대로 타입으로 추론해요. 어느 모듈에서 왔는지에 관계없이 클라이언트의 autocomplete가 따라오는 거죠.

더 알아보기

  • 라우터 초기화와 구성 옵션: Define Routers
  • 컨텍스트로 procedure 간 공통 데이터 처리: Context