빠른 시작
이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
tRPC는 REST와 GraphQL의 개념을 결합합니다. 둘 중 하나에 익숙하지 않다면 핵심 개념을 살펴보세요.
설치
tRPC는 여러 패키지로 분리되어 있어 필요한 부분만 설치할 수 있습니다. 원하는 패키지를 코드베이스의 적절한 위치에 설치해야 합니다. 이 빠른 시작 가이드에서는 간단하게 기본 클라이언트만 사용하겠습니다. 프레임워크별 가이드는 React에서 사용하기와 Next.js에서 사용하기를 참조하세요.
- tRPC는 TypeScript >= 4.7.0 버전이 필요합니다
- 공식적으로 비엄격 모드를 지원하지 않으므로
tsconfig.json에서"strict": true설정을 강력히 권장합니다
@trpc/server와 @trpc/client 패키지를 설치하는 것으로 시작합니다:
- npm
- yarn
- pnpm
- bun
shnpm install @trpc/server @trpc/client
shnpm install @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
백엔드 라우터 정의하기
tRPC로 타입 안전한 API를 구축하는 단계를 차근차근 살펴보겠습니다. 시작으로 이 API에는 다음 TypeScript 시그니처를 가진 세 개의 엔드포인트가 포함될 것입니다:
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. 라우터 인스턴스 생성하기
먼저 tRPC 백엔드를 초기화합니다. 별도 파일에서 작업하고 전체 tRPC 객체 대신 재사용 가능한 헬퍼 함수를 내보내는 것이 좋은 관행입니다.
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
다음으로 appRouter라고 일반적으로 불리는 메인 라우터 인스턴스를 초기화합니다. 여기에 나중에 절차를 추가할 것입니다. 마지막으로 클라이언트 측에서 사용할 라우터 타입을 내보내야 합니다.
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
2. 쿼리 절차 추가하기
라우터에 쿼리 절차를 추가하려면 publicProcedure.query()를 사용하세요.
다음은 데이터베이스에서 사용자 목록을 반환하는 userList 쿼리 절차를 생성하는 예시입니다:
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
3. 입력 파서를 사용하여 절차 입력 검증하기
userById 절차를 구현하려면 클라이언트의 입력을 받아야 합니다. tRPC는 입력을 검증하고 파싱하기 위한 입력 파서를 정의할 수 있게 합니다. 사용자 정의 입력 파서를 만들거나 zod, yup, superstruct 같은 검증 라이브러리를 선택할 수 있습니다.
입력 파서는 publicProcedure.input()에 정의하며, 아래와 같이 리졸버 함수에서 접근할 수 있습니다:
- Vanilla
- Zod
- Yup
- Valibot
The input parser should be a function that validates and casts the input of this procedure. It should return a strongly typed value when the input is valid or throw an error if the input is invalid.
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any ZodType, e.g. z.string() or z.object().
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any YupSchema, e.g. yup.string() or yup.object().
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
With Valibot, you simply need to wrap your schema with TypeSchema.
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
본 문서의 남은 부분에서는 검증 라이브러리로 zod를 사용할 것입니다
4. 변형(mutation) 절차 추가하기
GraphQL과 유사하게 tRPC는 쿼리와 변형 절차를 구분합니다.
서버 측 절차의 작동 방식은 쿼리와 변형 간에 크게 다르지 않습니다. 메서드 이름이 다르고 클라이언트가 이 절차를 사용하는 방식이 변경되지만 그 외에는 모두 동일합니다!
라우터 객체에 새 속성으로 userCreate 변형을 추가해 보겠습니다:
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
API 서빙하기
이제 라우터를 정의했으므로 서빙할 수 있습니다. tRPC에는 다양한 어댑터가 있어 원하는 백엔드 프레임워크를 사용할 수 있습니다. 간단하게 하기 위해 standalone 어댑터를 사용하겠습니다.
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
See the full backend code
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
클라이언트에서 새 백엔드 사용하기
이제 클라이언트 측 코드로 이동하여 엔드투엔드 타입 안전성의 힘을 경험해 보겠습니다. 클라이언트에서 사용할 AppRouter 타입을 가져오면 구현 세부사항을 클라이언트에 노출시키지 않으면서도 시스템 전체에 완벽한 타입 안전성을 확보할 수 있습니다.
1. tRPC 클라이언트 설정
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
tRPC의 링크(Link)는 GraphQL의 링크와 유사하게 데이터가 서버로 전송되기 전에 데이터 흐름을 제어할 수 있게 해줍니다. 위 예제에서는 httpBatchLink를 사용하는데, 이 링크는 여러 호출을 자동으로 단일 HTTP 요청으로 묶어줍니다. 링크에 대한 더 깊은 활용법은 링크 문서를 참조하세요.
2. 쿼리 및 뮤테이션 실행
이제 trpc 객체를 통해 API 프로시저에 접근할 수 있습니다. 직접 사용해 보세요!
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
완전한 자동 완성
인텔리센스(Intellisense)를 열어 프론트엔드에서 API를 탐색해 보세요. 모든 프로시저 경로와 호출 방법이 그대로 제공되는 것을 확인할 수 있습니다.
client/index.tsts// Full autocompletion on your routestrpc .u ;
client/index.tsts// Full autocompletion on your routestrpc .u ;
직접 사용해 보세요!
다음 단계
선호하는 프레임워크에 tRPC를 설치하는 방법을 배우려면 예제 앱을 꼭 확인해 보시기 바랍니다.
기본적으로 tRPC는 Date 같은 복합 타입을 JSON 호환 타입_(예: Date는 string으로)_으로 변환합니다. 타입 무결성을 유지하려면 Data Transformer로 superjson 사용하기가 가장 간편한 방법입니다.
tRPC는 React 프로젝트와 Next.js를 위해 특별히 설계된 고급 클라이언트 측 도구를 제공합니다.