🔷 GraphQL0. GraphQL의 기초01 — What is GraphQL

01 — What is GraphQL

한 줄 답: GraphQL은 타입 시스템을 가진 질의 언어 사양그 사양을 해석해 응답을 만들어내는 실행 엔진의 묶음이다. Facebook이 만든 라이브러리도, REST 대체재도 아니다.


Why — 왜 이 정의가 중요한가

GraphQL을 처음 만나는 사람은 거의 항상 두 가지 중 하나로 오해한다.

흔한 오해현실
”GraphQL = Facebook이 만든 라이브러리”GraphQL은 언어 사양(spec)이고, 라이브러리는 구현체다 — graphql-js, Apollo Server, Hot Chocolate(.NET), gqlgen(Go) 등 수십 개
”GraphQL = REST 대체재”GraphQL은 데이터 페칭 패러다임이고, REST는 HTTP 리소스 모델이다 — 같은 레이어가 아니다
”GraphQL = JSON API”응답이 JSON일 뿐, 그 모양은 요청이 결정한다 — 서버가 모양을 강제하는 JSON API와 정반대

이 셋을 분리하면 “Apollo가 사라지면 GraphQL도 끝나나?”(아니다), “REST에서 GraphQL로 마이그레이션해야 하나?”(상황에 따라), “왜 응답이 매번 다를까?”(요청이 다르니까) 같은 질문들이 동시에 풀린다.


How — 어떻게 동작하나

1) 두 개의 분리된 레이어

GraphQL을 한 단어로 부르지만, 실제로는 수직으로 분리된 두 레이어다.

  • Layer 1 — Specification: spec.graphql.org에서 관리하는 문서. 문법(BNF), 타입 시스템, validation 규칙, 실행 알고리즘이 적혀 있다.
  • Layer 2 — Implementation: spec을 어떤 언어로 구현한 런타임. 같은 쿼리를 같은 스키마에 대해 실행하면 어떤 구현이든 같은 결과가 나와야 한다.
  • Layer 3 — Server framework: 구현 위에 라우팅·인증·플러그인을 얹은 제품. Apollo Server는 graphql-js 위에 있다.

2) Single Endpoint — 하나의 URL만 받는다

REST가 /users/123, /users/123/posts, /users/123/posts/456/comments처럼 경로로 리소스를 구분한다면, GraphQL은 모든 요청이 같은 URL로 간다.

POST https://api.example.com/graphql
Content-Type: application/json

{
  "query": "{ user(id: 123) { name posts { title } } }",
  "variables": {}
}

이 endpoint 하나 안에서 모든 데이터를 받아낸다. 어떤 데이터를 받을지는 path가 아니라 body 안의 query string이 정한다.

3) 세 가지 Operation

GraphQL은 클라이언트가 보낼 수 있는 요청을 정확히 세 종류로 정의한다.

Operation의미HTTP 비유
query데이터 읽기. 부수 효과 없음, 병렬 실행 가능GET
mutation데이터 쓰기. 부수 효과 있음, 순차 실행POST/PUT/DELETE
subscription서버가 밀어주는 스트림. WebSocket/SSE 위에서 동작(REST 비유 없음)
query GetUser($id: ID!) {
  user(id: $id) { name email }
}
 
mutation UpdateName($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) { id name }
}
 
subscription OnNewMessage($room: ID!) {
  messageAdded(room: $room) { id text author }
}

세 operation은 spec에 박힌 키워드다 — 임의로 늘릴 수 없다.

4) 클라이언트가 응답의 모양을 선언한다

이것이 GraphQL의 진짜 핵심이다.

# 요청
{
  user(id: 123) {
    name
    posts {
      title
    }
  }
}
// 응답 — 요청과 *동형*이다
{
  "data": {
    "user": {
      "name": "Alice",
      "posts": [
        { "title": "Hello" },
        { "title": "World" }
      ]
    }
  }
}

email을 요청하지 않았으니 응답에도 email이 없다. REST라면 서버가 결정한 모양이 고정으로 내려오지만, GraphQL은 요청이 응답의 스키마를 지정한다.


What — 구체 사양

GraphQL Spec October 2021 — 정의되는 것들

GraphQL October 2021 spec (현재 안정 버전)은 다음을 정의한다.

영역내용
Language쿼리 문법(BNF), 토큰, 주석, 이름 규칙
Type SystemScalar(Int/Float/String/Boolean/ID), Object, Interface, Union, Enum, InputObject, List, Non-Null
Introspection__schema, __type, __typename 메타 필드
Validation100+ 정적 규칙 (필드 존재, 타입 호환, fragment 순환 금지 등)
Execution쿼리 → 응답 변환 알고리즘 (resolver 호출 순서, 병렬성, 에러 전파)
Responsedata / errors / extensions 3개 최상위 필드

Spec이 정의하지 않는

영역왜 빠졌나
TransportHTTP·WebSocket·gRPC — 어떤 전송이든 OK (graphql-over-http는 별도 표준)
Authentication사양 외 — 보통 헤더로 처리
Caching응답이 매번 다르니 HTTP 캐시 무용 — 구현이 알아서 (Apollo Client cache 등)
Schema languagespec은 introspection 형식만 정의 — SDL(type User { ... })은 권고지만 의무 아님
PaginationConnection/Edges 패턴은 관행(Relay spec)이지 GraphQL spec 아님

→ 즉 GraphQL spec은 “쿼리가 들어오면 어떻게 검증하고 실행해서 어떤 모양의 응답을 만들어내야 하는가” 만 정의한다. 그 외는 모두 생태계의 합의다.

최소 동작 예시

# 스키마 (SDL)
type Query {
  hello(name: String!): String!
}
# 쿼리
{ hello(name: "GraphQL") }
// 응답
{ "data": { "hello": "Hello, GraphQL" } }

서버는 위 스키마를 기반으로 introspection 응답을 제공할 수 있어야 하고, hello 필드에 대한 resolver를 가지고 있어야 한다. 그 둘이 충족되면 spec을 따른다.


What-if — 잘못 이해하면

1) “GraphQL = Apollo”라고 믿으면

→ Apollo는 server framework + client library + SaaS다. GraphQL spec과는 독립이다. 대응: Apollo Server 대신 GraphQL Yoga, Mercurius, Hot Chocolate로 갈아도 쿼리는 그대로 동작한다.

2) Single endpoint를 하나의 HTTP 핸들러로만 보면

→ rate limiting·로깅·캐싱을 path 기반으로 짜둔 인프라가 모두 깨진다. 대응: GraphQL 전용 관측(operation name·complexity·depth) 도입.

3) 모든 응답이 200 OK인 것에 놀라면

→ GraphQL은 spec상 partial success를 허용한다. errors 배열에 에러가 들어 있어도 data는 부분적으로 채워진다. 대응: 클라이언트가 dataerrors둘 다 보고 판단하도록 짠다.

4) “쿼리 = 데이터베이스 쿼리”라고 오해하면

→ GraphQL 쿼리는 resolver 호출 트리다. DB 한 방 쿼리가 아니다. 대응: N+1 문제를 미리 알고 DataLoader 도입 (이 KB의 03-n-plus-1-dataloader 챕터).

5) Subscription을 WebSocket으로만 보면

→ spec은 transport를 정하지 않는다. WebSocket이 가장 흔하지만, SSE/HTTP long-poll도 가능. 대응: 클라이언트와 서버가 같은 transport spec(graphql-ws 또는 graphql-sse)을 쓰는지 확인.


Insight — 흥미로운 이야기

”SuperGraph라는 이름의 프로토타입”

2012년 2월, Facebook 본사 회의실에서 Nick Schrock이 동료들에게 보여준 첫 코드는 SuperGraph라는 이름이었다. Facebook의 News Feed가 객체 그래프인데, 그것을 관계형으로 평탄화해서 REST로 내보내고 다시 클라이언트에서 그래프로 복원하는 짓을 그만두자는 발상이었다 — “그래프인 채로 들고 다니면 안 되나?"

"왜 ‘Graph’-QL인가”

이름의 ‘Graph’는 데이터베이스의 그래프 DB와는 무관하다. Facebook 내부에서 도메인 데이터를 Open Graph라 부른 데서 왔다 — 페이지·사람·관계가 노드와 엣지로 연결된다는 관점. 즉 GraphQL은 그래프 데이터를 쿼리하는 언어지, 그래프 DB 전용 언어가 아니다. 백엔드가 PostgreSQL이든 MongoDB든 외부 REST API든 상관없다.

”Facebook이 놓아준 시점”

2018년 11월 6일, Facebook은 GraphQL 상표·저작권·도메인을 모두 Linux Foundation 산하 GraphQL Foundation으로 이관했다. 동일한 시점에 Airbnb·Apollo·GitHub·Shopify·Twitter 등이 founding member로 합류했다. 언어를 만든 회사가 손을 떼는 순간, 그 언어는 비로소 표준이 된다 — Java가 Oracle 손을 떠나지 못해 받은 비판을 GraphQL은 받지 않는다.


요약 + 다이어그램

GraphQL은 spec이고, 런타임은 그 spec의 구현이다. 하나의 endpoint에 query/mutation/subscription 세 operation이 오가며, 클라이언트가 응답의 모양을 선언한다. Facebook이 만들었지만 2018년 이후로는 GraphQL Foundation이 관리한다.

다음 문서: 02-history-and-spec.mdx — 이 언어는 누가 만들었고, 지금은 누가 관리하나?