현대 클라이언트와 백엔드 서버간의 소통 방법 중 가장 대중적인 두 축은 바로 "REST API"와 "GraphQL"입니다. 이 둘은 각기 다른 목적을 갖고 탄생했으며 차별화된 장단점을 가집니다. 이번 포스트에서는 두 방식의 핵심 원리부터 실제 코드 예시, 그리고 어떤 상황에 무엇을 선택해야 하는지까지 깊이 있게 다루어보겠습니다.

1. REST API: 자원(Resource) 기준의 아키텍처

HTTP URI를 통해 자원을 명시하고 HTTP Method (GET, POST, PUT, DELETE)를 통해 해당 자원에 대한 연산을 조작하는 디자인 철학입니다. REST(Representational State Transfer)는 2000년 Roy Fielding의 박사 논문에서 처음 정의되었으며, 6가지 핵심 제약 조건을 따르는 API를 RESTful API라고 부릅니다.

REST API의 URI 설계 원칙을 살펴보면, 자원은 명사로 표현하고 행위는 HTTP 메서드로 표현합니다.

# 올바른 REST API URL 설계
GET    /users          # 전체 유저 목록 조회
GET    /users/42       # ID가 42인 유저 조회
POST   /users          # 새 유저 생성
PUT    /users/42       # ID가 42인 유저 전체 수정
PATCH  /users/42       # ID가 42인 유저 부분 수정
DELETE /users/42       # ID가 42인 유저 삭제

# 중첩 자원 (관계 표현)
GET    /users/42/posts        # 유저 42의 게시글 목록
GET    /users/42/posts/7      # 유저 42의 7번 게시글
POST   /users/42/posts        # 유저 42의 새 게시글 작성

HTTP 상태 코드도 REST API의 중요한 구성 요소입니다. 응답의 의미를 표준화된 코드로 전달합니다.

  • 2xx (성공): 200 OK (조회/수정 성공), 201 Created (생성 성공), 204 No Content (삭제 성공)
  • 4xx (클라이언트 오류): 400 Bad Request (잘못된 요청), 401 Unauthorized (인증 필요), 403 Forbidden (권한 없음), 404 Not Found (자원 없음)
  • 5xx (서버 오류): 500 Internal Server Error (서버 내부 오류), 503 Service Unavailable (서비스 불가)

REST API의 장점은 HTTP의 기존 인프라(캐싱, 로드 밸런싱 등)를 그대로 사용할 수 있다는 것입니다. 개발자들에게 가장 익숙하며 사실상 전 세계 API 표준에 가깝고, 직관적인 URL 구조로 문서화 없이도 어느 정도 이해가 가능합니다. Swagger/OpenAPI를 이용한 문서 자동화도 잘 갖춰져 있습니다.

단점은 특정 화면을 그릴 때 필요한 데이터보다 훨씬 많은 데이터를 받아오게 되는 오버페칭(Over-fetching)이나, 한 번의 호출로 부족하여 여러 번 API를 날려야 하는 언더페칭(Under-fetching) 문제입니다. 예를 들어 사용자 프로필 페이지를 그리기 위해 /users/42, /users/42/posts, /users/42/followers를 각각 호출해야 하는 상황이 발생합니다.

2. GraphQL (GQL)

페이스북이 2012년에 내부 사용을 위해 개발하고 2015년 오픈소스로 공개한 데이터 질의 언어입니다. 클라이언트가 "나는 이 화면에 유저의 이름과 게시물 번호 딱 2개가 필요해"라고 서버에 GQL로 질의하면, 서버는 정확히 요구된 데이터만 담아서 내려보냅니다.

GraphQL은 단 하나의 엔드포인트(/graphql)를 통해 모든 요청을 처리합니다. 클라이언트가 원하는 데이터의 형태를 쿼리로 직접 정의합니다.

# GraphQL 쿼리 예시 - 필요한 필드만 정확히 요청
query GetUserProfile($userId: ID!) {
  user(id: $userId) {
    name
    email
    posts(limit: 5) {
      id
      title
      createdAt
    }
    followers {
      count
    }
  }
}

# 응답 - 요청한 구조 그대로 반환
{
  "data": {
    "user": {
      "name": "Midori",
      "email": "midori@example.com",
      "posts": [
        { "id": "1", "title": "Hello World", "createdAt": "2026-01-01" }
      ],
      "followers": { "count": 128 }
    }
  }
}

뮤테이션(Mutation)은 데이터를 변경하는 GraphQL 작업입니다.

# GraphQL 뮤테이션 - 데이터 변경
mutation CreatePost($title: String!, $content: String!) {
  createPost(title: $title, content: $content) {
    id
    title
    createdAt
  }
}

GraphQL의 장점은 오버페칭, 언더페칭 문제를 단일(Endpoint) 호출로 해결하며 클라이언트 개발의 자율성이 대폭 올라갑니다. 여러 자원을 하나의 요청으로 가져올 수 있어 특히 모바일 클라이언트의 네트워크 효율이 크게 향상됩니다. 강타입 스키마(Schema)를 통해 API 계약이 명확하고, GraphQL Playground 등을 통한 대화형 문서화가 가능합니다.

단점으로는 HTTP 수준에서의 캐싱이 어렵습니다(POST 기반 단일 엔드포인트 특성상). 클라이언트가 악의적으로 매우 무겁고 복잡한 깊이(Depth)의 쿼리를 요청할 경우 백엔드가 과부하될 수 있어 쿼리 깊이 제한, 비용 분석(Cost Analysis) 같은 추가 보호 장치가 필요합니다. 또한 팀 전체가 GraphQL 생태계를 학습해야 하는 초기 진입 비용도 있습니다.

3. N+1 문제: GraphQL의 대표적인 함정

GraphQL을 사용할 때 자주 마주치는 성능 이슈가 있습니다. 유저 목록을 가져올 때 각 유저의 게시글도 함께 조회한다고 가정해봅시다. 단순하게 구현하면 유저 N명에 대해 각각 게시글 쿼리가 1번씩 더 실행되어 총 N+1번의 데이터베이스 쿼리가 발생합니다.

이를 해결하기 위해 DataLoader 패턴을 사용합니다. 요청들을 배치(Batch)로 묶어 한 번의 쿼리로 처리하는 방식으로, GraphQL 서버에서 거의 필수적으로 사용되는 패턴입니다.

// DataLoader로 N+1 문제 해결
const DataLoader = require('dataloader');

const userPostsLoader = new DataLoader(async (userIds) => {
  // 여러 userId를 한 번의 DB 쿼리로 처리
  const posts = await db.posts.findAll({
    where: { userId: { [Op.in]: userIds } }
  });
  // userId별로 그룹화하여 반환
  return userIds.map(id => posts.filter(p => p.userId === id));
});

4. REST vs GraphQL 실용적 비교

두 방식을 주요 기준으로 직접 비교해보겠습니다.

  • 러닝 커브: REST가 월등히 낮습니다. HTTP를 이해하면 바로 사용 가능합니다. GraphQL은 스키마 정의, 리졸버, N+1 문제 등 학습이 필요합니다.
  • 캐싱: REST가 유리합니다. GET 요청으로 표준 HTTP 캐싱(CDN, 브라우저 캐싱)이 자연스럽게 적용됩니다.
  • 데이터 유연성: GraphQL이 유리합니다. 클라이언트가 필요한 데이터를 정확히 요청할 수 있습니다.
  • 실시간 데이터: GraphQL의 Subscription 기능으로 실시간 데이터 업데이트를 표준화된 방식으로 처리할 수 있습니다. REST는 WebSocket이나 SSE를 별도로 구성해야 합니다.
  • 타입 안전성: GraphQL은 스키마 자체가 엄격한 타입 시스템의 역할을 합니다. REST는 OpenAPI 스펙을 별도로 작성해야 합니다.
  • 파일 업로드: REST가 자연스럽습니다. GraphQL에서의 파일 업로드는 별도 처리가 필요합니다.

결론: 언제 무엇을 선택할까?

간결성과 명확한 체계, 쉬운 스케일링, 팀의 빠른 온보딩이 필요하다면 REST API가 좋은 선택입니다. 전통적인 CRUD 위주의 서비스, 공개 API, 캐싱이 중요한 경우, 팀이 GraphQL 숙련도가 낮은 경우 특히 적합합니다.

반면 클라이언트(모바일, 웹, TV 등)의 종류가 다양하고 각각 필요한 데이터 형태가 달라 유연한 데이터 요청이 필수적이라면, 아니면 프론트엔드 팀이 백엔드 변경 없이 자유롭게 데이터를 조합해야 하는 상황이라면 GraphQL 도입이 합리적인 결정이 될 수 있습니다. 실제로 GitHub, Shopify, Facebook 등 복잡한 데이터 관계를 가진 대형 서비스들이 GraphQL을 활용하고 있습니다.

두 방식은 상호 배타적이지 않습니다. 기존 REST API를 유지하면서, 복잡한 데이터 조합이 필요한 특정 기능에만 GraphQL을 도입하는 하이브리드 접근도 가능합니다.