이런 분이 읽으면 좋습니다
요약: REST, GraphQL, tRPC 는 경쟁이 아니라 레이어가 다르다. 공개 API 에는 REST, TypeScript 풀스택 내부 통신에는 tRPC, 복잡한 다중 소스 집계에는 GraphQL 이 맞는다. 대부분의 2026년 SaaS 는 이 중 2개를 조합한다.
이 글은 새 프로젝트의 API 레이어를 설계하는 백엔드·풀스택 개발자를 위해 썼다. 세 기술의 2026년 4월 기준 생태계 현황을 숫자와 코드로 비교하고, “어떤 상황에 어떤 걸 쓰는가” 판단 기준을 정리한다.
왜 지금 이 선택이 어려운가
2025년 초까지만 해도 “REST vs GraphQL” 이 논쟁의 전부였다. 2026년에는 tRPC 가 TypeScript 생태계에서 빠르게 확산하면서 선택지가 셋으로 늘었다. 채용 공고 기준 REST 가 70%, GraphQL 이 25%(2025년 초 40%에서 하락), tRPC 가 15%(상승 중)다. 셋 다 프로덕션에서 쓰이고 있고, 셋 다 특정 상황에서 최적이다.
문제는 “어떤 게 좋은가”가 아니라 **“우리 팀의 제약 조건에서 어떤 조합이 후회를 줄이는가”**다.
빠른 비교
| REST | GraphQL | tRPC | |
|---|---|---|---|
| 타입 안전성 | OpenAPI codegen 필요 | codegen 필요 (5~15초 빌드) | 제로 codegen (즉시 추론) |
| HTTP 캐싱 | 네이티브 (GET 캐시) | 불가 (POST 단일 엔드포인트) | 제한적 (커스텀 필요) |
| 공개 API 적합성 | 최적 | 가능 | 불가 (TS 전용) |
| 프런트엔드 유연성 | 오버페칭 위험 | 정확한 필드 선택 | 타입 추론으로 자동 |
| 운영 복잡도 | 낮음 | 높음 (쿼리 보안·APM) | 낮음 |
| 학습 곡선 | 낮음 | 중간~높음 | 낮음 (TS 안다면) |
| 다중 언어 클라이언트 | 모든 언어 | 모든 언어 | TypeScript 전용 |
REST — 공개 API 의 기본값
언제 쓰는가: 외부 개발자에게 API 를 공개할 때, 다중 언어 클라이언트를 지원할 때, HTTP 캐싱이 중요할 때.
REST 의 진짜 강점은 기술이 아니라 생태계다. 모든 언어, 모든 HTTP 클라이언트, 모든 CDN, 모든 모니터링 도구가 REST 를 이해한다. GET /products/123 은 브라우저 캐시, CDN 캐시, 프록시 캐시가 모두 자동으로 동작한다. 이걸 GraphQL 이나 tRPC 로 재현하려면 별도 인프라가 필요하다.
// Fastify REST 엔드포인트 — 간결하고 캐시 친화적
app.get('/api/products/:id', async (req, reply) => {
const product = await db.product.findUnique({
where: { id: req.params.id },
});
if (!product) return reply.code(404).send({ error: 'Not found' });
reply.header('Cache-Control', 'public, max-age=60');
return product;
}); 2026년 현황: OpenAPI 3.1 + TypeScript codegen(openapi-typescript, orval) 조합이 성숙해서 타입 안전성 격차가 줄었다. 다만 codegen 스텝이 개발 루프에 5~15초를 추가한다.
GraphQL — 복잡한 프런트엔드의 집계 레이어
언제 쓰는가: 프런트엔드가 여러 백엔드 소스의 데이터를 한 번에 조합해야 할 때, 모바일 앱에서 대역폭을 아끼고 싶을 때.
GraphQL 의 핵심 가치는 클라이언트가 필요한 필드만 정확히 요청하는 것이다. 대시보드처럼 한 화면에 사용자·주문·알림·통계를 모두 보여줘야 하는 경우, REST 로는 4번 요청하거나 커스텀 엔드포인트를 만들어야 한다. GraphQL 은 쿼리 하나로 해결한다.
# 한 번의 쿼리로 대시보드에 필요한 데이터를 정확히 가져온다
query DashboardData($userId: ID!) {
user(id: $userId) {
name
plan
}
recentOrders(userId: $userId, limit: 5) {
id
total
status
}
unreadNotifications(userId: $userId) {
count
}
} 숨은 비용: 쿼리 깊이 제한, 복잡도 분석, persisted queries 설정, 전용 APM(Apollo Studio 등)이 필요하다. 단순 CRUD API 에 GraphQL 을 도입하면 REST 대비 서버 CPU 20~40% 추가 소비 + 운영 도구 비용이 발생한다. 복잡도가 이점을 정당화하는지 먼저 따져야 한다.
tRPC — TypeScript 풀스택의 게임 체인저
언제 쓰는가: 클라이언트와 서버가 모두 TypeScript 이고, 같은 리포(또는 모노레포)에 있을 때. 내부 API 전용.
tRPC 는 스키마 파일도, codegen 도, API 명세 문서도 없다. 서버에서 함수를 작성하면 클라이언트에서 즉시 타입이 추론된다. 함수 시그니처를 바꾸면 클라이언트에서 빨간 줄이 뜬다 — 빌드를 기다릴 필요 없이.
// tRPC 라우터 — 함수만 쓰면 클라이언트 타입이 자동으로 따라온다
export const productRouter = router({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
const product = await db.product.findUnique({
where: { id: input.id },
});
if (!product) throw new TRPCError({ code: 'NOT_FOUND' });
return product; // 반환 타입이 클라이언트에서 자동 추론
}),
}); 제약: tRPC 는 TypeScript 전용이다. Python, Go, Swift 클라이언트는 쓸 수 없다. 공개 API 로 노출할 수 없다. 이 제약이 치명적이면 REST 나 GraphQL 을 써야 한다. 하지만 Next.js + tRPC 같은 풀스택 TypeScript 구성에서는 개발 속도와 안전성이 압도적이다.
실전 조합 패턴
대부분의 2026년 SaaS 는 하나만 쓰지 않는다. 흔한 조합:
REST + tRPC
장점
- 공개 API 는 REST 로 안정적 제공
- 내부 프런트엔드는 tRPC 로 빠르게 개발
- 운영 복잡도 낮음
단점
- 두 레이어 유지 비용
- REST 쪽 타입 동기화에 codegen 필요
3~10인 TypeScript 팀의 가장 흔한 조합
REST + GraphQL
장점
- 공개 API 와 복잡한 내부 UI 모두 커버
- 언어 무관 클라이언트 지원
단점
- GraphQL 운영 비용 높음
- 두 API 스타일의 인력 필요
대규모 팀 또는 다중 클라이언트(웹+모바일+파트너) 환경
tRPC 단독
장점
- 최소 복잡도
- 최고 개발 속도
- 타입 안전성 100%
단점
- 공개 API 제공 불가
- TypeScript 외 클라이언트 불가
내부 도구, MVP, 풀스택 TypeScript 사이드 프로젝트
이것만은 피하자 — 흔한 실수 4가지
의사결정 기준
- 공개 API 가 필요한가? → REST 는 무조건 포함. 추가로 내부 통신을 tRPC 나 GraphQL 로 보강.
- 클라이언트가 전부 TypeScript 인가? → tRPC 가 내부 통신 최우선 후보.
- 대시보드처럼 다중 소스 집계 화면이 핵심인가? → GraphQL 이 이점을 발휘하는 영역.
- 3~5인 팀인가? → 복잡도를 낮춰라. REST + tRPC 조합이 유지 비용 대비 가장 생산적이다.
- 확신이 없으면? → REST 단독으로 시작해라. 가장 적은 가정, 가장 넓은 호환성, 가장 쉬운 전환 경로.
다음에 읽을 글
- AWS vs GCP vs Azure: 2026 스타트업 비용 비교 — API 레이어를 골랐으면, 그 위에 올라갈 클라우드 비용도 비교해야 한다
- Supabase vs PlanetScale vs Neon: SaaS 에 맞는 Postgres 선택 — API 아래 DB 레이어 선택
- 프로덕션 AI 서비스의 프롬프트 버전 관리 — tRPC 로 AI 기능을 감쌀 때의 버전 관리 패턴