GraphQL 명세 함께 읽기: 왜 스펙을 읽어야 할까
GraphQL로 API를 몇 번 만들어보셨다면 쿼리를 작성하고, 스키마를 정의하고, 리졸버를 붙이는 일에는 꽤 익숙하실 텐데요. 그런데 혹시 흔히 “스펙”이라고도 부르는 GraphQL 공식 명세(specification) 를 직접 펼쳐본 적 있으신가요? 아마 대부분은 “그런 게 있다는 건 아는데, 실무에서 Apollo만 잘 쓰면 되지 않나?”라고 생각하셨을 것 같습니다. 😅
저도 오랫동안 그랬습니다. 그런데 막상 명세를 읽어보니, 평소에 “왜 이렇게 동작하지?” 하고 넘어갔던 수많은 질문에 대한 답이 거기 다 적혀 있더라고요. 마침 2025년 9월, GraphQL 명세가 September 2025 에디션으로 정식 발행되었습니다. 2021년 10월 이후 4년 만의 정식 개정판인데요. 이 글을 시작으로 여러 편에 걸쳐 GraphQL 명세를 처음부터 끝까지 함께 읽어보려고 합니다.
이번 첫 편에서는 본격적으로 문법을 파고들기 전에, “명세가 대체 무엇을 정하는 문서인지”, 그리고 “왜 우리가 이걸 읽어야 하는지”를 먼저 짚어보겠습니다.
GraphQL은 “쿼리 언어이자 실행 런타임”입니다
명세의 가장 첫 문장은 GraphQL을 이렇게 정의합니다. “GraphQL은 API를 위한 쿼리 언어이며, 기존 데이터로 그 쿼리를 실행하는 런타임이다.” 짧지만 GraphQL의 정체성이 다 담긴 문장인데요. 여기서 두 가지 키워드를 떼어놓고 봐야 합니다.
우선 쿼리 언어(query language) 라는 점입니다. 우리가 작성하는 다음과 같은 요청문은 그 자체로 하나의 언어입니다.
{
user(id: "1") {
name
email
}
}
이건 JSON도 아니고 자바스크립트 객체도 아닙니다. GraphQL이라는 독자적인 언어로 작성된 문서(document)인데요. 그래서 명세에는 이 언어를 위한 어휘(token)와 문법(grammar)이 프로그래밍 언어 명세처럼 엄밀하게 정의되어 있습니다.
그리고 실행 런타임(runtime) 이라는 점입니다. 위 쿼리를 받은 서버는 정해진 규칙에 따라 각 필드를 해석하고, 데이터를 채워 넣고, 결과를 조립합니다. 이 “어떻게 실행할 것인가”의 규칙도 전부 명세에 적혀 있습니다. 그래서 어떤 언어로 구현하든, 어떤 라이브러리를 쓰든 같은 쿼리에는 같은 모양의 응답이 나오는 겁니다.
정리하면 GraphQL은 단순한 데이터 포맷이 아니라, 요청을 기술하는 언어 + 그 요청을 처리하는 실행 규칙의 묶음입니다. 이 두 축이 바로 명세의 뼈대를 이룹니다.
명세가 일부러 정하지 않는 것들
명세를 읽을 때 의외로 중요한 건, 명세가 무엇을 정하느냐만큼이나 무엇을 일부러 정하지 않느냐입니다. GraphQL 명세는 다음 세 가지에 대해서는 침묵합니다.
우선 전송 방식(transport) 입니다. “GraphQL은 반드시 HTTP를 써야 한다”는 규칙은 명세 어디에도 없습니다. 우리가 흔히 POST /graphql로 요청을 보내는 건 관례일 뿐, 명세상으로는 HTTP든 WebSocket이든 심지어 함수 호출이든 상관없습니다. 그래서 같은 GraphQL 서버를 HTTP로도 부르고 구독(subscription)은 WebSocket으로 처리하는 일이 가능한 거죠.
또한 저장소(storage) 입니다. 명세는 데이터가 어디서 오는지 전혀 관여하지 않습니다. PostgreSQL이든 REST API든 메모리 안의 배열이든, 심지어 여러 마이크로서비스를 조합한 결과든 GraphQL은 신경 쓰지 않습니다. 명세는 “필드에 값을 채우는 함수가 있다”고만 가정하고, 그 함수가 데이터를 어디서 가져오는지는 구현자에게 완전히 맡깁니다.
마지막으로 구현 언어입니다. 명세는 특정 프로그래밍 언어에 묶여 있지 않습니다. 그래서 JavaScript의 Apollo Server뿐 아니라 Java, Go, Rust, Python 등 어떤 언어로도 GraphQL 서버를 만들 수 있고, 모두 같은 명세를 따릅니다.
이렇게 전송, 저장소, 언어를 비워둔 덕분에 GraphQL은 특정 기술 스택에 종속되지 않는 범용 표준이 될 수 있었습니다. 명세를 읽다가 “왜 HTTP 얘기가 안 나오지?” 싶다면, 그건 빠진 게 아니라 의도적으로 비워둔 자리라고 이해하시면 됩니다.
September 2025: 4년 만에 도착한 정식 개정판
이번에 우리가 기준으로 삼을 판본은 September 2025 에디션인데요. 잠깐 GraphQL 명세의 역사를 짚고 가면 이 판본이 왜 의미 있는지 보입니다.
GraphQL은 2012년 페이스북 내부에서 시작해 2015년에 외부로 공개되었고, 2018년 6월에 첫 공식 명세가 나왔습니다. 그 뒤 2021년 10월 에디션이 발표되었는데, September 2025는 바로 그 2021년 판 이후 4년 만의 정식 개정판입니다. 공개 10주년을 맞아 그동안 쌓여 있던 변경 제안들이 한꺼번에 반영된 셈이죠.
이번 판에서 새로 정식 편입된 굵직한 항목으로는 스키마의 특정 위치를 정확히 가리키는 표기법인 스키마 좌표(Schema Coordinates), 여러 입력 중 정확히 하나만 받도록 강제하는 @oneOf 입력 객체(OneOf input objects), 쿼리·뮤테이션 같은 실행 문서에도 설명문(description)을 달 수 있게 된 기능 등이 있습니다. 흥미롭게도 이 변경들은 상당수가 사람이 아니라 도구와 LLM이 GraphQL을 더 잘 다루도록 돕는 방향을 향하고 있는데요. 이 부분은 시리즈 마지막 편에서 따로 깊이 다루겠습니다.
지금 당장 모든 신규 기능을 외울 필요는 없습니다. 다만 우리가 읽는 문서가 “낡은 2018년 버전”이 아니라 가장 최신 표준이라는 점만 기억해두시면 좋겠습니다.
명세는 여덟 개의 장으로 이루어져 있습니다
명세 본문은 크게 여덟 개의 장(section)으로 구성됩니다. 각 장이 무엇을 다루는지 미리 한눈에 보면, 앞으로의 시리즈가 어떤 지도를 따라가는지 감이 잡히실 겁니다.
- Overview — GraphQL이 무엇이고 어떤 설계 원칙을 갖는지 소개하는 도입부입니다. 지금 이 글이 다루는 영역이기도 하죠.
- Language — 우리가 작성하는 쿼리 문서의 문법입니다. 필드, 인자, 프래그먼트, 변수, 디렉티브가 여기 정의됩니다.
- Type System — 서버가 제공하는 스키마의 타입들입니다. 스칼라, 객체, 인터페이스, 유니온, 열거형, 입력 객체가 여기 있습니다.
- Introspection — 스키마가 자기 자신을 설명하는 방법입니다. GraphiQL이나 코드 생성 도구가 동작하는 비밀이 여기 있습니다.
- Validation — 쿼리를 실행하기 전에 올바른지 검사하는 규칙들입니다.
- Execution — 검증을 통과한 쿼리를 실제로 실행해 데이터를 채우는 과정입니다.
- Response — 실행 결과를 어떤 모양으로 돌려줄지 정하는 응답 형식입니다.
- Appendix — 표기 규칙과 문법 요약 같은 참고 자료입니다.
이 시리즈는 이 순서를 그대로 따라갑니다. 다음 편부터 GraphQL 쿼리 언어(Language)를 시작으로 타입 시스템, 인트로스펙션, 검증, 실행, 응답을 차례로 읽고, 마지막에 September 2025에서 새로 들어온 기능들을 한 번에 정리하겠습니다. 시간순으로 읽으면 자연스럽게 기초에서 심화로 이어지도록 구성했으니, 순서대로 따라오시면 됩니다.
하나의 쿼리가 명세를 따라 흐르는 길
여덟 개의 장이 따로 노는 게 아니라, 실제로는 요청 하나가 이 장들을 순서대로 통과합니다. 예를 들어 다음 요청을 보냈다고 해볼게요.
{
user(id: "1") {
name
}
}
서버는 우선 이 텍스트가 올바른 GraphQL 문서인지 문법에 맞춰 해석합니다. 이게 Language 장의 역할인데요. 그다음 이 쿼리가 스키마와 맞아떨어지는지, 그러니까 user라는 필드가 실제로 존재하고 거기서 name을 요청해도 되는지 검사합니다. 여기에는 Type System과 Validation 장이 함께 관여합니다. 검사를 통과하면 각 필드의 값을 채우는 함수를 호출해 데이터를 모으는데, 이 과정이 Execution 장입니다. 마지막으로 그 결과를 정해진 형식에 담아 돌려주는 일을 Response 장이 맡습니다.
{
"data": {
"user": {
"name": "김철수"
}
}
}
요청한 필드의 모양 그대로 응답이 돌아오는 이 깔끔함이 GraphQL의 매력인데요. 그 뒤에서는 방금 본 것처럼 여러 장의 규칙들이 톱니바퀴처럼 맞물려 돌아가고 있습니다. 시리즈를 따라가다 보면 이 톱니바퀴 하나하나가 어떻게 생겼는지 차근차근 보이실 겁니다.
그래서, 명세를 왜 읽어야 할까요
여기까지 읽고도 “그래도 실무에선 라이브러리가 알아서 해주는데 굳이?”라는 생각이 드실 수 있습니다. 그래서 명세를 읽으면 구체적으로 뭐가 달라지는지 말씀드리고 싶은데요.
우선 “왜 이렇게 동작하지?”에 답할 수 있게 됩니다. 예를 들어 Non-Null로 선언한 필드에서 에러가 나면 왜 그 필드만 null이 되는 게 아니라 부모 객체까지 통째로 null이 되는지, 뮤테이션은 왜 여러 개를 보내면 순서대로 실행되는데 쿼리는 그렇지 않은지 같은 질문들 말이죠. 이런 동작은 전부 명세에 명확한 이유와 함께 규정되어 있습니다. 라이브러리 문서만 봐서는 좀처럼 만나기 어려운 답들입니다.
또한 특정 라이브러리에 종속되지 않는 지식을 얻습니다. Apollo의 동작과 GraphQL 표준의 동작을 구분할 수 있게 되면, 나중에 다른 서버 구현으로 갈아타거나 새로운 도구를 평가할 때 흔들리지 않습니다. “이건 Apollo가 추가한 기능이고, 저건 표준이구나”를 가려낼 수 있게 되는 거죠.
그리고 명세서를 읽는 경험 자체가 자산이 됩니다. GraphQL 명세는 분량이 방대하지 않으면서도 잘 정돈된, 읽기 좋은 표준 문서입니다. 평소에 RFC나 언어 명세 같은 1차 자료를 읽는 게 부담스러우셨다면, GraphQL 명세는 그 연습을 시작하기에 딱 좋은 교재입니다.
마치며
이번 편에서는 GraphQL 명세가 “쿼리 언어 + 실행 런타임”을 정의하는 문서라는 점, 전송, 저장소, 구현 언어는 일부러 비워둔다는 점, 그리고 September 2025 에디션이 4년 만의 정식 개정판이라는 점을 살펴봤습니다. 명세 전체가 여덟 개의 장으로 이루어져 있다는 지도도 함께 그려봤고요.
다음 편에서는 본격적으로 명세 제2장 GraphQL 쿼리 언어(Language)로 들어가, 우리가 매일 작성하는 쿼리 문서가 어떤 문법 규칙 위에 서 있는지 하나씩 뜯어보겠습니다. 그 전에 직접 쿼리를 만져보고 싶으시다면 GraphQL API를 간단하게 호출하는 방법을 먼저 읽어보시는 것도 좋습니다.
명세 원문이 궁금하시다면 GraphQL September 2025 명세를 직접 펼쳐보세요. 생각보다 술술 읽히는 문서라는 걸 느끼실 겁니다.
This work is licensed under
CC BY 4.0