GraphQL vs REST API 실전 비교: 2년간 운영 경험에서 배운 것들
최근 2년간 다양한 프로젝트에서 GraphQL과 REST API를 모두 운영해본 경험을 바탕으로 두 기술을 비교합니다. API 설계 선택은 단순히 기술 트렌드가 아니라 프로젝트 특성과 팀 환경에 맞춰 결정해야 합니다.
우리 팀의 상황
2022년, 새로운 프로젝트를 시작하면서 API 설계 방식을 결정해야 했습니다:
- 프로젝트 A: 복잡한 대시보드 앱, 다양한 데이터 조합 필요
- 프로젝트 B: 단순 CRUD 중심 관리자 페이지
- 프로젝트 C: 모바일 앱 백엔드, 대역폭 최적화 중요
결론적으로 A와 C는 GraphQL, B는 REST를 선택했습니다. 그 이유를 설명합니다.
GraphQL의 장점
1. Over-fetching/Under-fetching 해결
REST에서 흔히 겪는 문제:
// REST: 사용자 정보 + 주문 내역이 필요할 때
GET /api/users/123 // 불필요한 필드까지 모두 받음
GET /api/users/123/orders // 추가 요청 필요
// GraphQL: 필요한 것만 한 번에
query {
user(id: "123") {
name
email
orders(limit: 5) {
id
totalAmount
status
}
}
}모바일 앱(프로젝트 C)에서는 이 차이가 데이터 전송량 40% 감소로 이어졌습니다.
2. 강력한 타입 시스템
# GraphQL Schema
type User {
id: ID!
name: String!
email: String!
role: UserRole!
orders: [Order!]!
createdAt: DateTime!
}
enum UserRole {
ADMIN
USER
GUEST
}
type Query {
user(id: ID!): User
users(filter: UserFilter, pagination: PaginationInput): UserConnection!
}스키마 자체가 문서이자 계약입니다. TypeScript와 결합하면 프론트엔드-백엔드 타입 일관성을 보장할 수 있습니다.
3. 자체 문서화
GraphQL Playground나 Apollo Studio에서 스키마를 바로 탐색할 수 있습니다. Swagger 같은 별도 문서 도구 없이도 API 명세를 확인할 수 있어 문서 유지보수 비용이 제로에 가깝습니다.
4. 실시간 구독 지원
// 채팅 메시지 실시간 구독
subscription OnNewMessage($roomId: ID!) {
messageAdded(roomId: $roomId) {
id
content
sender {
name
avatar
}
createdAt
}
}WebSocket 기반 Subscription으로 실시간 기능을 쉽게 구현할 수 있습니다.
REST API의 장점
1. 단순성과 직관성
GET /api/products # 목록 조회
GET /api/products/123 # 단일 조회
POST /api/products # 생성
PUT /api/products/123 # 수정
DELETE /api/products/123 # 삭제엔드포인트 구조가 명확하고, HTTP 메서드만으로 의도를 파악할 수 있습니다. 주니어 개발자도 바로 이해할 수 있는 직관성이 있습니다.
2. HTTP 캐싱 활용
# REST: HTTP 캐시 헤더 활용
GET /api/products/123
Cache-Control: max-age=3600
ETag: "abc123"
# GraphQL: POST 요청이라 HTTP 캐싱 불가
POST /graphql
{body: {query: "..."}} # 매번 서버 처리 필요REST는 CDN 캐싱을 쉽게 적용할 수 있어, 읽기 중심 API에서 큰 성능 이점이 있습니다.
3. 파일 업로드 간편
// REST: 표준 multipart/form-data
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Profile photo');
await fetch('/api/upload', { method: 'POST', body: formData });
// GraphQL: 별도 스펙(graphql-upload) 필요
// 설정이 복잡하고 일부 환경에서 호환성 이슈4. 디버깅 도구 풍부
Postman, Insomnia, curl 등 성숙한 도구 생태계가 있습니다. 문제 발생 시 재현과 디버깅이 용이합니다.
실제 운영에서 겪은 이슈
GraphQL의 함정: N+1 쿼리
// 이 쿼리가 실행되면...
query {
users {
name
orders {
id
}
}
}
// 내부적으로 이렇게 됨 (N+1 문제)
SELECT * FROM users;
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 2;
SELECT * FROM orders WHERE user_id = 3;
... (사용자 수만큼 반복)해결책: DataLoader 패턴
const orderLoader = new DataLoader(async (userIds) => {
const orders = await Order.findAll({
where: { userId: userIds }
});
// userIds 순서에 맞게 그룹핑하여 반환
return userIds.map(id => orders.filter(o => o.userId === id));
});REST의 함정: API 버전 관리
기능 추가 시 하위 호환성 유지가 어렵습니다:
/api/v1/users # 기존
/api/v2/users # 새 필드 추가
/api/v3/users # 구조 변경
# 결국 여러 버전을 동시에 유지해야 함GraphQL은 스키마 확장이 자연스러워 버전 없는 API가 가능합니다.
선택 가이드라인
| 상황 | 추천 |
|---|---|
| 복잡한 데이터 관계, 다양한 클라이언트 | GraphQL |
| 단순 CRUD, 빠른 개발 필요 | REST |
| 모바일 앱 (대역폭 중요) | GraphQL |
| 퍼블릭 API, 외부 개발자 대상 | REST |
| 실시간 기능 필요 | GraphQL |
| 레거시 시스템 연동 | REST |
결론
"GraphQL이 더 좋다" 또는 "REST가 정답"이라는 이분법은 틀렸습니다.
프로젝트의 복잡도, 팀의 경험, 클라이언트 요구사항을 고려하여 선택해야 합니다. 때로는 같은 서비스 내에서 두 가지를 함께 사용하는 것도 좋은 전략입니다.