TRPC 적용기

6 minute read
2024-07-24

설치

npm i @tanstack/react-query @trpc/react-query @trpc/client @trpc/server


추가로


npm i zod


스케폴딩

src
├── app
│   ├── _trpc
│   └── api

├── provider
│   └── TrpcProvider.tsx

└── server
    ├── caller.ts
    ├── index.ts
    └── trpc.ts


server

우선 가장 먼저 trpc.ts 를 만들어준다. 여기서 컨텍스트를 지니는 싱글톤 객체인 t 를 만들어준다.


src/server/trpc.ts
import { initTRPC } from '@trpc/server';
 
/**
 * Initialization of tRPC backend
 * Should be done only once per backend!
 */
const t = initTRPC.create();
/**
 * Export reusable router and procedure helpers
 * that can be used throughout the router
 */
export const router = t.router;
export const publicProcedure = t.procedure;
export const createCallerFactory = t.createCallerFactory;


싱글톤 객체인 t 는 항상 하나의 인스턴스로 관리되어야 한다. 때문에 생성한 컨텍스트를 기준으로 하는 router, procedure, callerFactory 를 여기서 export 해주고 다른 곳에서 사용하도록 해준다.


그런 다음 핵심이 되는 appRouter 를 만들어준다. 방금 생성한 export 한 router 를 이용한다.


src/server/index.ts
import { publicProcedure, router } from './trpc';
 
export const appRouter = router({
  // ...
})
 
export type AppRouter = typeof appRouter;


router 인자로 handler 함수를 넣어줄 수 있다.


handler example
router({
  
  getNumList: publicProcedure.query(async () => {
    const nums = Array.from({ length: 10 }, (_, i) =>
      i % 2 !== 0 ? i : i * 2
    );
    return nums;
  }),
 
 
  add: publicProcedure
    .input(
      z.object({
        one: z.number(),
        two: z.number(),
      })
    )
    .query(async (opts) => {
      const { input } = opts;
      const { one, two } = input;
 
      const result = await new Promise<number>((r) =>
        setTimeout(() => r(one + two), 1200)
      );
 
      return result;
    }),
})


API 등록

이렇게 만들어 준 api 를 접근 가능하도록 등록해줘야 한다.


app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server';
 
 
const getHandler = (req: Request) => {
  // NOTE: return 으로 NextResponse 를 해야하는데, 그 역할을 `fetchRequestHandler` 가 해줌
  return fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => ({}),
  });
};
 
export { getHandler as GET };


관련 공식문서


이렇게 세팅했다면, 브라우저로 직접 요청 가능해진다.


image-20240724153055578


그렇다면 이제 컴포넌트 에서 요청가능하도록 해줘야 한다. 클라이언트 단에서 이용하기 위한 객체가 필요하다.


client

app/_trpc/client.ts
import { AppRouter } from '@/server';
import { createTRPCReact } from '@trpc/react-query';
 
export const trpc = createTRPCReact<AppRouter>({});


이를 이용해 Trpc 용 Provider 를 따로 만들어준다.


src/provider/TrpcProvider.tsx
'use client';
 
import { trpc } from '@/app/_trpc/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import React, { PropsWithChildren, useState } from 'react';
 
const TrpcProvider = ({ children }: PropsWithChildren) => {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: '/api/trpc',
        }),
      ],
    })
  );
 
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  );
};
 
export default TrpcProvider;
 


link 에 관련된 건 공식문서를 확인해보면 좋다.


이렇게 만든 Provider 를 layout.ts 에 당연히 적용해줘야 한다.


app/layout.ts
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <TrpcProvider>
        <body className={inter.className}>{children}</body>
      </TrpcProvider>
    </html>
  );
}


이렇게 클라이언트는 아래와 같이 사용 가능해진다.


'use client';
 
const Acomp = () => {
  const { data } = trpc.getNumList.useQuery();
}


이렇게 클라이언트에서 타입스크립트 기반의 간편한 rpc 가 이용가능해졌다. 그렇다면 server component 에선 어떻게 해야할까?


Server

아까 initTRPC 로 생성된 싱글톤 객체을 이용해 export 한 createCallerFactory 가 필요하다.


src/server/caller.ts
import { appRouter } from '.';
import { createCallerFactory } from './trpc';
 
const createCaller = createCallerFactory(appRouter);
 
export const caller = createCaller({});


여기서 생성한 caller 를 가지고 서버 컴포넌트에서 아래와 같이 사용 가능해진다.


Server Component
import { caller } from '@/server/caller';
 
export default async function Home() {
  const data = await caller.getNumList();
  
  // ...
}


참고한 영상