tRPC vs REST: 타입 안전한 API 계층 선택

타입 안전과 외부 공개 사이에서 계약 방식을 고르는 기준

tRPC vs REST API 선택 썸네일

핵심 요약

  1. tRPC는 서버 타입을 클라이언트에 그대로 공유해 코드 생성 없이 계약 불일치를 컴파일 단계에서 잡는다.
  2. tRPC는 TS 단일 코드베이스·내부 소비자에 강하고, 다언어·외부 공개에는 REST/OpenAPI가 맞는다.
  3. HTTP 캐시·버전 관리·게이트웨이 정책은 REST가 유리하다.
  4. 혼합 전략에서는 '소비자가 누구인가'로 경계를 정하고, 내부 RPC가 의도치 않게 외부에 노출되지 않도록 관리한다.

타입스크립트로 프론트와 백엔드를 함께 만든다면, API 응답 타입이 백엔드 변경과 어긋나 런타임에서 터지는 문제를 겪어 봤을 것이다. tRPC는 이 문제를 코드 생성 없이 타입으로 해결하지만, 모든 상황에 맞는 만능은 아니다. 외부에 API를 공개해야 하거나 여러 언어의 클라이언트가 붙는다면 이야기가 달라진다. 이 글은 두 접근이 각각 무엇을 강제하고 무엇을 포기하는지 비교해 선택 기준을 정리한다.

API 계약을 타입으로 강제한다는 것

API 계약은 "어떤 입력을 보내면 어떤 출력이 오는가"에 대한 약속이다. REST에서는 이 약속을 문서나 스키마로 관리하고, 클라이언트는 그에 맞춰 타입을 만든다. tRPC 공식 Concepts 문서에 따르면 appRouter와 publicProcedure로 정의한 서버 라우터 타입(AppRouter)이 그대로 클라이언트에 전달되어, 별도 스키마 정의나 코드 생성 없이 호출부에서 타입 검사가 이뤄진다. 계약이 어긋나면 배포 전에 컴파일 단계에서 드러난다.

tRPC가 잘 맞는 조건

tRPC의 전제는 "서버와 클라이언트가 같은 타입스크립트 코드베이스의 타입을 공유한다"는 것이다. 이 전제가 성립할 때 개발 속도가 크게 오른다.

  • 모노레포나 타입을 공유하는 구성에서 프론트와 백엔드를 한 팀이 함께 개발한다.
  • 소비자가 자사 프론트엔드뿐이고, 외부 공개 API가 아니다.
  • 스키마 정의와 코드 생성 단계를 줄이고 싶다.
  • React Query 같은 데이터 패칭 계층과 자연스럽게 연동하고 싶다.

모노레포·동일 팀·내부 소비자

tRPC는 라우터를 정의하면 클라이언트가 그 타입을 추론한다. 엔드포인트를 추가할 때 별도 스키마 파일을 갱신하지 않아도 되고, 리팩터링 시 호출부가 함께 깨져 누락을 막는다. 대신 서버와 클라이언트가 같은 런타임·버전에 묶이므로, 배포가 분리된 환경에서는 타입 불일치를 다루는 추가 설계가 필요하다.

REST/OpenAPI가 유리한 조건

REST는 HTTP 메서드와 경로, 상태 코드라는 보편 규칙 위에 서 있다. OpenAPI로 스키마를 정의하면 다양한 언어의 클라이언트를 생성할 수 있고, 문서·목 서버·검증 도구가 생태계로 연결된다. 다음 조건에서는 REST가 낫다.

  • 외부 개발자나 제휴사에 API를 공개해야 한다.
  • 여러 언어·플랫폼 클라이언트가 있다.
  • HTTP 캐시, CDN, 게이트웨이 정책을 그대로 활용해야 한다.
  • 계약을 버전 단위로 관리하고 하위 호환을 보장해야 한다.

캐싱·버전 관리·외부 공개 비교

두 접근의 차이는 운영 단계에서 더 분명해진다.

관점tRPCREST/OpenAPI
계약 정의서버 타입에서 자동 추론OpenAPI 스키마 명시
다언어 클라이언트사실상 TS에 한정코드 생성으로 다언어 지원
HTTP 캐시경로·메서드 의미 부재로 제한적메서드·헤더 기반 캐시 활용
버전 관리코드 변경과 함께 이동경로·미디어 타입으로 명시
외부 공개부적합적합

특히 캐싱은 실무 차이가 크다. REST는 GET 응답에 캐시 헤더를 붙여 CDN·프록시에서 재사용할 수 있지만, RPC 스타일 호출은 이런 의미를 갖지 않아 캐시 계층을 별도로 설계해야 한다.

혼합 전략과 경계 설정

둘 중 하나를 전체에 강제할 필요는 없다. 사내 대시보드·관리 도구처럼 소비자가 명확한 영역은 tRPC로 빠르게 만들고, 외부 공개 API나 서드파티가 붙는 경계는 REST/OpenAPI로 두는 혼합이 현실적이다. 경계는 "소비자가 누구인가"로 정한다. 같은 데이터라도 외부에 나가는 순간 계약은 문서화·버전 관리 대상이 된다.

// 내부: tRPC 라우터 (타입 공유)
export const appRouter = router({
  listOrders: publicProcedure.query(() => db.orders.findMany())
});

// 외부: OpenAPI 스키마로 공개
// GET /v1/orders  -> 스키마·버전 관리 대상

혼합 시 주의할 점은 내부 편의를 위해 만든 RPC 엔드포인트가 습관적으로 외부에 노출되는 것이다. 공개 경계를 명시적으로 관리하지 않으면 tRPC 라우터 전체가 의도치 않게 외부에 열릴 수 있다.

타입 안전을 유지하는 현실적인 절차

REST를 선택하더라도 타입 안전을 포기할 필요는 없다. openapi-typescript로 OpenAPI 스키마에서 클라이언트 타입을 생성하고, 서버 응답이 스키마와 일치하는지 검증하는 단계를 CI에 넣는다. 스펙 기반 타입 생성 절차는 OpenAPI 스펙으로 타입을 생성하고 API 계약 지키기에서 단계별로 확인할 수 있다. 이렇게 하면 스키마가 단일 출처이자 검증 기준이 된다. 반대로 tRPC를 선택하면 외부 소비자가 생기는 시점을 미리 표시해 두고, 그때 REST 경계를 추가하는 계획을 세운다. 두 방식 모두 "계약이 어디에 정의되어 있는가"를 한곳으로 모으는 것이 핵심이다. 계약이 코드·문서·타입 세 곳에 흩어지면 어느 것이 최신인지 알 수 없게 된다. 데이터 패칭과 서버 상태를 다루는 방식은 App Router 데이터 흐름 글과 함께 보면 계약과 캐시 경계를 정리하기 좋다.

선택 후 되돌리기 비용

tRPC는 소비자가 늘어날 때 확장 비용이 커진다. 반대로 REST는 초기 스키마·코드 생성 설정 비용이 들지만 소비자가 늘어도 부담이 완만하다. 되돌리기 관점에서는 "지금의 소비자가 앞으로도 하나일 것인가"를 기준으로 판단한다. 내부 전용이 확실하면 tRPC, 외부 확장 가능성이 있으면 REST를 기본으로 두는 편이 안전하다. API 계층은 데이터 패칭·캐시 전략과 맞물리므로, 프론트 상태 관리 결정과 함께 검토하는 것이 좋다.

초보자가 자주 실수하는 포인트

  • 외부 공개 API에 tRPC를 적용해 클라이언트 언어와 버전 관리에서 막힌다.
  • 내부 RPC 엔드포인트를 공개 경계 구분 없이 노출한다.
  • 캐시 요구가 큰 공개 데이터를 RPC 스타일로만 제공해 CDN·프록시 캐시를 활용하지 못한다.

체크리스트

  • 현재와 예상 소비자가 내부 전용인지 외부 공개인지 확인했다.
  • 계약 정의(타입 공유 vs OpenAPI 스키마) 방식을 정했다.
  • 캐싱·버전 관리·외부 공개 요구를 기준으로 우열을 판단했다.
  • 혼합 시 공개 경계와 노출 금지 엔드포인트를 명시했다.
  • 소비자 증가 시 되돌리기·확장 비용을 검토했다.

자주 묻는 질문

tRPC를 쓰면 REST API가 필요 없나요?

내부 소비자만 있다면 그럴 수 있다. 하지만 외부 공개나 다언어 클라이언트가 생기면 REST/OpenAPI 경계가 필요해지는 경우가 많다.

tRPC는 캐싱이 안 되나요?

HTTP 메서드·경로 의미가 없어 CDN·프록시의 표준 캐시를 그대로 쓰기 어렵다. 클라이언트 데이터 계층의 캐시로 보완하거나, 캐시가 중요한 경로는 REST로 둔다.

둘을 같이 쓰는 것이 복잡하지 않나요?

경계를 소비자 기준으로 명확히 정하면 오히려 단순하다. 내부는 타입 공유로 빠르게, 외부는 스키마·버전 관리로 안정적으로 가져가는 분업이 된다.

참고 자료 · 검증 기준

위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.

이 글은 실제 실무 환경 테스트 및 검증된 기술 자료를 바탕으로 작성되었으며, 환경 변화에 따라 지속적으로 갱신됩니다.