tRPC 라우터 병합
tRPC 라우터 병합 (Merging Routers)
API 코드가 커지면 한 파일에 전부 넣기 어려워져요. tRPC는 라우터를 여러 파일로 쪼갰다가 다시 합치는 방법을 제공해요. 이렇게 하면 사용자·게시물 같은 도메인 단위로 코드를 정리할 수 있어요.
가장 단순한 방법은 한 라우터 안에 다른 라우터를 네임스페이스로 넣는 거예요.
// 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