- 공유 링크 만들기
- X
- 이메일
- 기타 앱
REST API로 설계하다 보면 화면 하나를 그리기 위해 엔드포인트를 3~4번 호출하거나, 불필요한 필드까지 받아오는 오버페칭(Over-fetching) 문제를 겪게 됩니다. GraphQL은 이런 비효율을 해결할 수 있는 대안이지만, 서버 복잡도와 학습 비용이라는 트레이드오프가 존재합니다. 이 글에서는 두 방식의 실무 장단점을 비교하고, 프로젝트 상황별로 어떤 선택이 합리적인지 판단 기준을 제시합니다.
| API 설계 고민 끝: REST API와 GraphQL 상황별 선택 전략과 하이브리드 구조 |
1. REST API: 표준화된 HTTP 기반 설계의 장단점
REST API는 HTTP 메서드(GET, POST, PUT, DELETE)와 리소스 중심의 URL 설계로 직관적이고 학습 곡선이 낮습니다. 대부분의 백엔드 프레임워크가 기본 지원하며, HTTP 캐싱(ETag, Cache-Control)을 그대로 활용할 수 있어 CDN이나 브라우저 캐시 전략을 세우기 쉽습니다.
REST의 주요 장점
- HTTP 레벨 캐싱 활용: GET 요청에 대해 브라우저·프록시·CDN 캐시를 자동으로 적용 가능
- 명확한 엔드포인트 설계: `/users`, `/posts/:id` 같은 리소스 단위 URL로 API 구조를 쉽게 파악
- 낮은 진입 장벽: 별도 스키마 파일이나 쿼리 언어 학습 없이 JSON over HTTP만으로 구현 가능
- 에러 핸들링 단순화: HTTP 상태 코드(200, 400, 500 등)로 성공/실패를 명확히 구분
REST의 한계
- 오버페칭/언더페칭: 클라이언트가 필요한 필드만 선택할 수 없어 불필요한 데이터를 받거나, 추가 요청을 해야 함
- 엔드포인트 증가: 화면별·디바이스별로 최적화된 응답을 제공하려면 엔드포인트가 계속 늘어남
- 깊은 중첩 데이터 처리: `User → Posts → Comments → Author` 같은 4~5단계 관계를 표현하려면 여러 번 호출하거나, 백엔드에서 복잡한 조인/DTO 설계 필요
권장 상황: 공개 API, 캐싱이 중요한 콘텐츠 서비스, 단순한 CRUD 중심 프로젝트, 팀 내 GraphQL 학습 비용을 감당하기 어려운 경우
2. GraphQL: 클라이언트 주도형 쿼리 언어의 실무 경험
GraphQL은 페이스북이 설계한 쿼리 언어로, 클라이언트가 필요한 데이터 구조를 직접 지정해 요청합니다. 단일 엔드포인트(`/graphql`)에서 모든 쿼리를 처리하며, 스키마(Schema)와 타입 시스템으로 프론트엔드-백엔드 간 계약을 명확히 정의합니다.
프론트엔드 개발자 관점: 생산성 향상
- 필드 단위 선택: `{ user { id name posts { title } } }` 같은 쿼리로 원하는 필드만 가져와 네트워크 효율 증가
- HTTP 요청 횟수 감소: 여러 리소스를 한 번의 쿼리로 조회 가능 (예: 사용자 정보 + 게시물 목록 + 댓글 통계)
- 깊은 객체 모델 처리 간소화: 4~5단계 중첩 데이터를 반복 파싱이나 별도 DTO 없이 바로 사용 가능
- 자동 문서화: GraphiQL, Apollo Studio 같은 도구로 스키마를 실시간 탐색하며 개발
백엔드 개발자 관점: 복잡도 증가
- 스키마 파일 작성 부담: 타입 정의, 리졸버(Resolver) 설계, 필드 간 관계 매핑 등 초기 설정 비용이 큼
- 쿼리 최적화 필수: N+1 문제 발생 시 DataLoader 같은 배치 로더를 직접 구현해야 함
- 서버 부하 제어: 클라이언트가 무거운 쿼리(깊이 10단계, 수천 개 필드)를 날릴 수 있어 쿼리 복잡도·깊이 제한 로직 필요
- 파일 업로드 구현 번거로움: GraphQL 표준에 파일 업로드 명세가 없어 멀티파트 처리나 별도 엔드포인트 혼용
- HTTP 캐싱 활용 어려움: 단일 POST 엔드포인트 특성상 URL 기반 캐시 전략을 쓸 수 없음 (Persisted Queries나 별도 캐시 계층 설계 필요)
에러 핸들링 복잡도
GraphQL은 하나의 쿼리 안에 여러 필드 요청이 섞여 있어, 일부는 성공하고 일부는 실패할 수 있습니다. 응답 구조가 `{ data: {...}, errors: [...] }` 형태로 분리되므로, 클라이언트에서 성공/실패를 필드별로 분기 처리해야 합니다.
권장 상황: 모바일 앱(네트워크 효율 중요), 복잡한 중첩 데이터 모델, 화면별로 다른 데이터 조합이 필요한 SPA, 실시간 업데이트(Subscription) 요구사항이 있는 프로젝트
3. 프로젝트 상황별 의사결정 기준
두 방식은 상호 배타적이지 않습니다. 실무에서는 공개 API는 REST로, 내부 클라이언트용 API는 GraphQL로 분리하거나, 파일 업로드 같은 특수 기능은 REST 엔드포인트로 남겨두는 하이브리드 설계도 흔합니다.
REST를 선택해야 할 때
- HTTP 캐싱이 핵심: 콘텐츠 조회가 많고 CDN 캐시 전략이 중요한 경우
- 단순한 CRUD: 리소스 간 관계가 복잡하지 않고, 화면별 데이터 조합이 단순한 경우
- 공개 API: 외부 개발자가 쉽게 이해하고 사용할 수 있도록 표준화된 인터페이스 제공이 필요한 경우
- 팀 역량: GraphQL 학습 비용을 감당하기 어렵거나, 기존 REST 인프라가 잘 구축된 경우
GraphQL을 선택해야 할 때
- 네트워크 효율: 모바일 환경에서 요청 횟수와 페이로드 크기를 최소화해야 하는 경우
- 복잡한 데이터 관계: 사용자 → 게시물 → 댓글 → 작성자 같은 4~5단계 중첩 구조를 자주 조회하는 경우
- 화면별 최적화: 같은 리소스를 화면·디바이스별로 다른 필드 조합으로 요청하는 경우
- 실시간 요구사항: Subscription을 활용한 실시간 업데이트가 필요한 경우
하이브리드 전략 예시
- 파일 업로드/다운로드: REST 엔드포인트로 처리 (GraphQL 멀티파트 처리 복잡도 회피)
- 공개 API: REST로 제공하여 외부 개발자 접근성 확보
- 내부 클라이언트: GraphQL로 프론트엔드 개발 생산성 극대화
- 캐싱 전략: 자주 조회되는 정적 데이터는 REST로 분리해 CDN 캐시 활용
4. 백엔드 설계 원칙: 통신 방식과 무관한 핵심
REST든 GraphQL이든, 백엔드 아키텍처는 레이어드 구조(Layered Architecture)로 설계해야 유지보수성과 확장성을 확보할 수 있습니다. 아래는 실무에서 검증된 디렉터리 구조와 설계 원칙입니다.
권장 프로젝트 구조
project-name/ ├── bin/ │ └── www # 서버 실행 파일 ├── node_modules/ # 의존성 모듈 ├── public/ # 정적 파일 (CSS, 이미지 등) │ ├── images/ │ ├── javascripts/ │ └── stylesheets/ ├── routes/ # 라우트 정의 (REST) 또는 GraphQL 스키마 │ ├── index.js │ └── users.js ├── controllers/ # 요청/응답 처리 (프리젠테이션 계층) ├── services/ # 비즈니스 로직 (도메인 계층) ├── models/ # 데이터베이스 모델 정의 (데이터 계층) ├── middlewares/ # 공통 미들웨어 (인증, 로깅 등) ├── views/ # 템플릿 파일 (ejs, pug 등) ├── app.js # 앱 초기화 및 설정 ├── package.json # 프로젝트 메타데이터 및 의존성 └── README.md # 프로젝트 설명
핵심 설계 원칙 3가지
- 컨트롤러와 서비스 분리: 컨트롤러는 요청/응답과 검증만 담당, 비즈니스 로직은 서비스 계층으로 분리 (테스트 작성 용이)
- 미들웨어 활용: 인증(JWT 검증), 로깅, 에러 핸들링 같은 공통 로직은 미들웨어로 분리하여 코드 중복 제거
- 유틸리티 분리: 날짜 포맷팅, 암호화, 유효성 검사 같은 재사용 로직은 별도 디렉터리(`utils/`, `helpers/`)로 관리
보안: 암호화와 복호화 필수 지식
- 암호화(Encryption): 평문(Plain Text)을 읽을 수 없는 암호문(Cipher Text)으로 변환하는 과정. 비밀번호를 입력값 그대로 DB에 저장하면 안 되며, 민감 정보는 반드시 암호화 필요
- 복호화(Decryption): 암호문을 평문으로 되돌리는 과정
- 단방향 해시 권장: 비밀번호는 bcrypt 같은 단방향 해시 함수로 저장 (복호화 불가능하게 설계)
- 키 관리: 암호화 키는 환경 변수나 AWS Secrets Manager 같은 비밀 관리 서비스로 관리
실무 팁: GraphQL 프로젝트에서도 리졸버는 컨트롤러처럼 얇게 유지하고, 실제 비즈니스 로직은 서비스 계층으로 위임하십시오. 이렇게 하면 나중에 REST 엔드포인트를 추가하거나, 배치 작업에서 같은 로직을 재사용할 때 중복 코드 없이 처리할 수 있습니다.
결론: 트레이드오프를 이해하고 선택하라
REST API는 HTTP 표준을 활용한 캐싱과 단순한 에러 핸들링이 강점이지만, 오버페칭과 엔드포인트 증가 문제를 감수해야 합니다. GraphQL은 클라이언트 유연성과 네트워크 효율을 제공하지만, 서버 복잡도(N+1 문제, 쿼리 제어, 캐싱 전략)와 학습 비용이라는 대가를 치러야 합니다. 정답은 없으며, 프로젝트의 요구사항(네트워크 환경, 데이터 복잡도, 팀 역량)에 따라 합리적으로 판단해야 합니다.
Action Item: 현재 진행 중인 프로젝트에서 클라이언트가 같은 리소스를 화면별로 다르게 조합해 사용하는 빈도를 측정하십시오. 3개 이상의 화면에서 서로 다른 필드 조합이 필요하다면 GraphQL 도입을 검토하고, 그렇지 않다면 REST로 시작해 HTTP 캐싱 전략에 집중하는 것을 권장합니다.
#함께 읽으면 좋은 글
React useEffect 무한 루프 해결법과 의존성 배열 완벽 가이드 : 바로보기
댓글
댓글 쓰기