본문 바로가기

tRPC 소개

· 1분 읽기
Alex / KATT 🐱
Creator of tRPC
비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

tRPC는 타입을 선언하지 않고도 (Node) 서버부터 클라이언트까지 종단간 타입 안전성을 제공합니다. 백엔드에서는 단순히 함수에서 데이터를 반환하기만 하면, 프론트엔드에서는 엔드포인트 이름을 기반으로 해당 데이터를 사용할 수 있습니다.

👋 저는 GitHub에서 "KATT"로 알려진 Alex입니다. tRPC라는 라이브러리를 소개하려 합니다. 아직 관련 글을 발행한 적은 없지만(이미 GitHub에서 >530 🌟를 달성했습니다), 시작을 알리는 간단한 소개를 작성합니다. 향후 문서와 동영상 소개가 예정되어 있으니 기대해 주세요! 최신 소식을 받거나 질문이 있다면 트위터 @alexdotjs를 팔로우해 주세요.

tRPC 엔드포인트 및 클라이언트 호출 시 모습 예시: Alt Text

React 전용 라이브러리(@trpc/react)를 제작했으며, 이는 훌륭한 react-query 위에서 동작합니다. 클라이언트 라이브러리(@trpc/client)는 React 없이도 작동합니다(Svelte/Vue/Angular/[..] 전용 라이브러리를 구축하고 싶다면 문의 주세요!).

코드 생성이 전혀 필요하지 않으며, 기존 Next.js/CRA/Express 프로젝트에 쉽게 통합할 수 있습니다.

예시​

다음은 string 인수를 받는 hello라는 tRPC 프로시저(엔드포인트) 예시입니다.

tsx
const appRouter = trpc.router().query('hello', {
input: z.string().optional(),
resolve: ({ input }) => {
return {
text: `hello ${input ?? 'world'}`,
};
},
});
export type AppRouter = typeof appRouter;
tsx
const appRouter = trpc.router().query('hello', {
input: z.string().optional(),
resolve: ({ input }) => {
return {
text: `hello ${input ?? 'world'}`,
};
},
});
export type AppRouter = typeof appRouter;

다음은 해당 데이터를 사용하는 타입 안전 클라이언트 예시입니다:

tsx
import type { AppRouter } from './server';
async function main() {
const client = createTRPCClient<AppRouter>({
url: `http://localhost:2022`,
});
const result = await client.query('hello', '@alexdotjs');
console.log(result); // --> { text: "hello @alexdotjs" }
}
main();
tsx
import type { AppRouter } from './server';
async function main() {
const client = createTRPCClient<AppRouter>({
url: `http://localhost:2022`,
});
const result = await client.query('hello', '@alexdotjs');
console.log(result); // --> { text: "hello @alexdotjs" }
}
main();

타입 안전성을 확보하는 데 필요한 전부입니다! result 타입은 백엔드 함수 반환 값에서 추론됩니다. 입력 데이터 또한 검증기 반환 값에서 추론되므로 데이터를 바로 안전하게 사용할 수 있습니다. 실제로 입력 데이터는 검증기를 통과해야 합니다(tRPC는 zod/yup/커스텀 검증기와 즉시 호환됩니다).

위 예시를 직접 확인할 수 있는 CodeSandbox 링크: https://githubbox.com/trpc/trpc/tree/main/examples/standalone-server (미리보기보다 터미널 출력을 확인해 주세요!)

잠깐? 백엔드 코드를 클라이언트로 임포트한다고? - 사실 아닙니다

겉보기에는 그렇게 보일 수 있지만, 서버에서 클라이언트로 실제 코드가 공유되지는 않습니다. TypeScript의 import type은 "[...] 타입 주석 및 선언에 사용할 선언만 임포트합니다. 항상 완전히 삭제되어 런타임에 흔적이 남지 않습니다." - TypeScript 3.8에 추가된 기능 - TypeScript 문서 참조.

코드 생성이 필요 없으며, 서버에서 클라이언트로 타입을 공유할 수 있는 환경만 있다면(모노레포 사용을 권장합니다) 바로 적용 가능합니다.

시작에 불과합니다!​

앞서 React 라이브러리가 있다고 언급했는데, React에서 위 데이터를 사용하는 방법은 다음과 같습니다:

tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);
tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);

.. 이제 클라이언트에서 타입 안전 데이터를 얻을 수 있습니다.

기존 브라운필드 프로젝트(Express/Next.js용 어댑터 제공)에 바로 tRPC를 추가할 수 있으며, CRA와 호환되고 React Native에서도 작동해야 합니다. React에 종속되지 않으므로 Svelte 또는 Vue 라이브러리를 개발하려면 연락주세요.

데이터 변경은 어떻게 하나요?​

변경(mutation)은 쿼리만큼 간단합니다. 내부적으로는 동일하지만 문법적 편의를 위해 다르게 노출되며, GET 요청 대신 HTTP POST 요청을 생성합니다.

다음은 데이터베이스를 사용한 조금 더 복잡한 예시로, todomvc.trpc.io / https://github.com/trpc/trpc/tree/main/examples/next-prisma-todomvc 에 있는 TodoMVC 예제에서 가져온 것입니다.

tsx
const todoRouter = createRouter().mutation('add', {
input: z.object({
id: z.string().uuid(),
data: z.object({
completed: z.boolean().optional(),
text: z.string().min(1).optional(),
}),
}),
async resolve({ ctx, input }) {
const { id, data } = input;
const todo = await ctx.task.update({
where: { id },
data,
});
return todo;
},
});
tsx
const todoRouter = createRouter().mutation('add', {
input: z.object({
id: z.string().uuid(),
data: z.object({
completed: z.boolean().optional(),
text: z.string().min(1).optional(),
}),
}),
async resolve({ ctx, input }) {
const { id, data } = input;
const todo = await ctx.task.update({
where: { id },
data,
});
return todo;
},
});

React 사용 예시는 다음과 같습니다:

tsx
const addTask = trpc.useMutation('todos.add');
return (
<>
<input
placeholder="What needs to be done?"
onKeyDown={(e) => {
const text = e.currentTarget.value.trim();
if (e.key === 'Enter' && text) {
addTask.mutate({ text });
e.currentTarget.value = '';
}
}}
/>
</>
)
tsx
const addTask = trpc.useMutation('todos.add');
return (
<>
<input
placeholder="What needs to be done?"
onKeyDown={(e) => {
const text = e.currentTarget.value.trim();
if (e.key === 'Enter' && text) {
addTask.mutate({ text });
e.currentTarget.value = '';
}
}}
/>
</>
)

지금은 여기까지.​

말씀드렸듯이, 시작에 불과합니다. 더 많은 기능들이 있습니다:

  • 사용자별 데이터를 위한 요청 컨텍스트 생성(의존성 주입 방식으로 리졸버에 전달) - 링크

  • 라우터용 미들웨어 지원 - 링크

  • 라우터 병합(모든 백엔드 로직을 단일 파일에 두지 않으려는 경우) - 링크

  • @trpc/next 어댑터를 이용한 React 생태계 최간단 서버 사이드 렌더링 - 링크

  • 타입 안전 오류 포매팅 - 링크

  • 데이터 변환기(Date/Map/Set 객체 직렬화 지원) - 링크

  • React Query 헬퍼 함수

시작하려면 Next.js 시작 가이드에 예제들이 준비되어 있습니다.

업데이트 소식을 받으려면 트위터에서 팔로우하세요!