GraphQL이란?

GraphQL은 Facebook이 2012년에 내부적으로 개발하고 2015년에 오픈소스로 공개한 API 쿼리 언어이자 런타임이다. 클라이언트가 필요한 데이터의 구조를 직접 명시하여 요청하고, 서버는 정확히 그 구조대로 응답한다.

REST와 달리 단일 엔드포인트(/graphql)로 모든 데이터 요청을 처리하며, 클라이언트가 응답의 형태를 결정한다는 점이 핵심 차이다.


REST와의 비교

항목RESTGraphQL
엔드포인트리소스별 다수 (/users, /orders)단일 (/graphql)
데이터 결정 주체서버가 응답 구조를 결정클라이언트가 필요한 필드를 명시
Over-fetching불필요한 필드까지 반환요청한 필드만 반환
Under-fetching연관 데이터 조회 시 추가 요청 필요한 번의 쿼리로 중첩 데이터 조회
버전 관리URL 버전(/v1/, /v2/)스키마 진화로 버전 없이 운영 가능
캐싱HTTP 캐싱 자연스러움별도 캐싱 전략 필요 (Persisted Query 등)
타입 시스템OpenAPI/Swagger로 별도 정의스키마 자체가 타입 시스템

핵심 개념

Schema

GraphQL API의 계약서다. 서버가 제공하는 모든 타입, 쿼리, 뮤테이션을 SDL(Schema Definition Language)로 정의한다.

type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}
 
type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
}
 
enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
}

Query

데이터를 읽는 연산이다. 클라이언트가 필요한 필드만 선택하여 요청한다.

query {
  user(id: "123") {
    name
    orders {
      total
      status
    }
  }
}

Mutation

데이터를 변경하는 연산이다. 생성, 수정, 삭제에 해당한다.

mutation {
  createOrder(input: { userId: "123", items: [...] }) {
    id
    status
  }
}

Subscription

서버에서 클라이언트로 실시간 데이터를 푸시하는 연산이다. WebSocket 기반으로 동작한다.

subscription {
  orderStatusChanged(orderId: "456") {
    status
  }
}

Type System

GraphQL은 강타입 시스템을 내장하고 있다. 스키마 자체가 API의 타입 정의이자 문서 역할을 한다.

타입설명예시
Scalar원시 타입Int, Float, String, Boolean, ID
Object필드의 집합type User { name: String! }
Enum열거형enum Status { ACTIVE, INACTIVE }
InputMutation 인자용 객체input CreateUserInput { name: String! }
Interface공통 필드 추상화interface Node { id: ID! }
Union여러 타입 중 하나union SearchResult = User | Order

!는 Non-Null을 의미하며, [Type!]!은 “null이 아닌 배열, 요소도 null 아님”을 뜻한다.


Resolver

스키마의 각 필드에 대해 실제 데이터를 반환하는 함수다. GraphQL 런타임은 쿼리를 파싱한 뒤, 각 필드에 매핑된 Resolver를 호출하여 결과를 조립한다.

Query.user(id) → DB에서 사용자 조회
User.orders   → 해당 사용자의 주문 목록 조회
Order.status  → 주문 상태 반환

Resolver는 부모 객체의 결과를 인자로 받으므로, 중첩된 필드도 순차적으로 해소된다.


N+1 문제와 DataLoader

GraphQL의 대표적 성능 함정이다. 목록 조회 시 각 항목마다 Resolver가 개별 호출되어, 1번의 목록 쿼리 + N번의 상세 쿼리가 발생한다.

해결 방법 — DataLoader 패턴:

  • 같은 틱(tick)에 발생한 개별 요청을 모아서 배치 쿼리 1회로 처리
  • Facebook이 GraphQL과 함께 공개한 패턴으로, 대부분의 언어에 구현체가 존재

Introspection

GraphQL 서버는 자기 자신의 스키마를 쿼리할 수 있는 인트로스펙션 기능을 내장한다.

{
  __schema {
    types { name }
  }
  __type(name: "User") {
    fields { name type { name } }
  }
}

이를 통해 GraphQL Playground, GraphiQL 같은 도구가 자동 완성과 문서화를 제공한다. 프로덕션 환경에서는 보안상 비활성화하는 것이 일반적이다.


Persisted Query

클라이언트가 매번 전체 쿼리 문자열을 보내는 대신, 사전에 등록된 쿼리의 해시만 전송하는 기법이다.

  • 성능: 네트워크 페이로드 감소
  • 보안: 허용된 쿼리만 실행 가능 (allowlist)
  • 캐싱: CDN 레벨 캐싱이 가능해짐

Federation

여러 GraphQL 서비스의 스키마를 하나의 통합 그래프로 합성하는 아키텍처 패턴이다. 각 서비스가 자신의 도메인에 해당하는 스키마 일부를 소유하고, Gateway가 이를 조합한다.

┌─────────┐
│ Gateway │ ← 클라이언트 요청 수신
└────┬────┘
     ├── User Service (User 타입 소유)
     ├── Order Service (Order 타입 소유)
     └── Product Service (Product 타입 소유)

MSA 환경에서 각 팀이 독립적으로 스키마를 관리하면서도, 클라이언트에는 단일 API로 제공할 수 있다.


관련 문서

  • Relay Specification — Cursor 페이지네이션, Node 인터페이스 등 GraphQL 사실상 표준 규격
  • gRPC — 또 다른 API 통신 규격 (RPC 기반)
  • protobuf — gRPC의 직렬화 포맷, GraphQL의 SDL과 대비되는 스키마 정의 방식