246. GraphQL - 클라이언트 주도형 유연한 API 쿼리 언어
⚠️ 이 문서는 기존 REST API가 "서버가 주는 대로 무조건 다 받아야 하는" 뷔페식 구조라서 모바일 앱의 데이터를 낭비(Overfetching)하거나, 반대로 정보가 모자라서 API를 5번씩 호출(Underfetching)해야 하는 참사를 막기 위해, **페이스북(Meta)이 만든 "클라이언트가 딱 자기가 원하는 데이터 항목(필드)만 장바구니에 골라 담아 API 1번만 찌르면 완벽하게 조립해서 던져주는 차세대 API 통신 언어인 'GraphQL'"**을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 서버(백엔드) 중심이었던 API 권력을 클라이언트(프론트엔드)에게 넘겨준 혁명이다. 프론트엔드 개발자가 JSON 형태의 빈칸 쿼리문(Query)을 쏘면, 서버가 그 빈칸에 데이터만 쏙 채워서 돌려주는 주문형(On-demand) 통신 규격이다.
- 가치:
이름과나이만 필요한데 REST API가주소, 결제 내역까지 1MB짜리 쓰레기 데이터를 몽땅 던져주어 폰 배터리와 데이터를 박살 내는 오버패칭(Overfetching) 문제를 완벽히 해결한다.- 기술 체계: 오직 1개의 엔드포인트(
/graphql)만 뚫어놓고, 읽기는Query, 쓰기/수정은Mutation, 실시간 알림은 **Subscription**이라는 3대 쿼리 문법으로 모든 REST API를 멸망시키고 대체한다.
Ⅰ. REST API의 붕괴: 오버패칭과 언더패칭의 늪
주는 대로 다 받아먹다간 폰 배터리가 터진다.
- 오버패칭 (Overfetching - 쓰레기 데이터 낭비):
- 인스타그램 모바일 앱에서 '친구 목록' 화면을 그린다. 프론트엔드는
GET /users/1을 호출한다. 나에게 필요한 건 오직 친구의이름과프로필 사진 URL딱 2개다. - 그런데 멍청한 REST 서버는
[이름, 프로필, 주소, 비밀번호, 10년간 결제 내역, 친구 100명 명단...]등 DB에 있는 그 유저의 모든 정보를 1MB짜리 거대 JSON 덩어리로 무조건 던져준다. - 모바일 요금제 데이터가 줄줄 새고, 이 쓰레기 데이터를 버리는 데 스마트폰 CPU가 낭비된다.
- 인스타그램 모바일 앱에서 '친구 목록' 화면을 그린다. 프론트엔드는
- 언더패칭 (Underfetching - 노가다 API 호출):
- 이번엔 '사용자 이름'과 '그 사용자가 쓴 최근 게시글 5개'를 같이 보여주는 화면이다.
- REST API는 한 번에 2개를 못 주니까, 프론트엔드는
GET /users/1을 찔러서 이름을 가져오고, 다시GET /users/1/posts를 찔러서 게시글을 가져온다. - 만약 그 게시글의 '댓글 쓴 사람 이름'까지 필요하다면? API를 100번 호출하는 미친 N+1 핑퐁 문제가 터져 앱이 멈춰버린다.
📢 섹션 요약 비유: REST API는 '정식 한정식 식당'입니다. 손님이 "김치(이름)만 주세요"라고 해도 무조건 상다리가 부러지게 50첩 반상(오버패칭)을 차려줘서 음식을 다 버리게 만듭니다. 반대로 "불고기랑 냉면 같이 주세요"라고 하면 "불고기 식당(API 1)과 냉면 식당(API 2)을 따로 갔다 오세요(언더패칭)"라고 똥개 훈련을 시키는 아주 비효율적인 시스템입니다.
Ⅱ. GraphQL의 등장: 클라이언트 주도형 장바구니
"내가 원하는 것만, 딱 1번의 주문서로 다 담아와라!"
- 단일 엔드포인트 (Single Endpoint):
- REST처럼
/users,/posts방을 수백 개 파지 않는다. GraphQL 서버는 오직POST /graphql이라는 문(Endpoint) 딱 하나만 열어둔다.
- REST처럼
- 원하는 필드만 쏙쏙 픽업 (No Overfetching):
- 프론트엔드가 서버에 보낼 때 아래와 같은 쿼리(주문서)를 날린다.
query { user(id: 1) { name profile_url } } - 서버는 이 주문서를 읽고 "아하! 이 녀석은
name이랑url딱 2개만 필요하구나!"라고 깨닫고, 불필요한 결제 내역은 DB에서 쳐다보지도 않고 딱 2개만 채운 초경량 JSON을 돌려준다. (모바일 데이터 폭발적 절약)
- 프론트엔드가 서버에 보낼 때 아래와 같은 쿼리(주문서)를 날린다.
- 그래프 연쇄 탐색 (No Underfetching):
- "내 이름과, 내가 쓴 게시글과, 그 게시글의 댓글들"을 1번의 API로 가져오고 싶다.
query { user(id: 1) { name posts { title comments { text } } } } - 프론트엔드가 이 거대한 연쇄 주문서 1장을 쏘면, 서버(GraphQL 엔진)가 뒤에서 DB를 지지고 볶아서 저 모양 그대로 완벽하게 조립된 1개의 JSON 덩어리를 만들어 한 방에 던져준다. API N번 호출의 악몽이 끝났다.
- "내 이름과, 내가 쓴 게시글과, 그 게시글의 댓글들"을 1번의 API로 가져오고 싶다.
📢 섹션 요약 비유: GraphQL은 '서브웨이(Subway) 샌드위치 주문서'입니다. 손님(프론트엔드)이 주문서 종이에 "빵은 오트밀, 야채는 오이 빼고, 소스는 칠리(내가 원하는 필드만 선택)"라고 체크해서 주방장(서버)에게 1장만 딱 줍니다. 주방장은 주문서에 없는 토마토(오버패칭 방지)는 절대 넣지 않고, 손님이 원하는 모든 재료를 샌드위치 1개(단일 응답)에 완벽히 조립해서 내어주는 극강의 커스텀(On-demand) 통신 기술입니다.
Ⅲ. GraphQL의 3대 무기와 백엔드의 고통 (Trade-off)
프론트엔드는 천국에 갔지만, 백엔드는 피를 토하고 있다.
- 3대 핵심 오퍼레이션 (Operations):
- Query (쿼리): REST의
GET과 같다. 데이터 읽어올 때 쓴다. - Mutation (뮤테이션): REST의
POST, PUT, DELETE다. 데이터를 조작하고 바꿀 때 쓴다. - Subscription (서브스크립션): 웹소켓(WebSocket)을 뚫어서, DB에 새 댓글이 달리면 프론트엔드에 실시간으로 알람을 쏴주는 기능이다.
- Query (쿼리): REST의
- 강력한 타입 시스템 (Schema & Types):
- GraphQL은 엉터리 통신을 막기 위해 백엔드에 스키마(Schema)를 강제한다. "나이는 무조건
Int, 이름은String!(필수)"라고 뼈대를 잡아둔다. - 프론트엔드가 나이에 "스무살"이라는 글자를 쏘면 서버가 1초 만에 문법 에러를 뱉어내어 통신 스펙 꼬임을 원천 방지한다.
- GraphQL은 엉터리 통신을 막기 위해 백엔드에 스키마(Schema)를 강제한다. "나이는 무조건
- 백엔드의 딜레마 (캐싱 불가와 N+1 폭탄):
- 프론트엔드가 맘대로 쿼리를 조립해서 쏘니까, 백엔드 서버 입장에서는 캐싱(Caching)을 하기가 미치도록 어렵다. (매번 요청 모양이 다름)
- 악의적인 프론트엔드가
유저 -> 친구 -> 그 친구의 친구 -> 또 그 친구의 친구라는 무한 루프 쿼리를 쏴버리면 백엔드 DB가 1초 만에 과부하로 뻗어버리는 대참사가 난다 (해결을 위해 DataLoader 같은 복잡한 최적화 툴을 억지로 써야 한다).
📢 섹션 요약 비유: GraphQL은 손님(프론트엔드)에게 메뉴판을 주지 않고 "주방에 있는 재료로 아무거나 다 만들어 달라고 적어내라!"라고 권력을 줘버린 뷔페식당입니다. 손님은 자기가 먹고 싶은 것만 쏙쏙 골라 1 접시에 담아오니 너무 행복합니다. 하지만 주방장(백엔드)은 손님이 도대체 무슨 해괴망측한 짬뽕 요리(복잡한 쿼리)를 시킬지 예측할 수 없어 재료를 미리 썰어놓을(캐싱) 수도 없고, 손님이 "요리 1만 인분 한 접시에 담아와!"라는 미친 주문(N+1 쿼리 공격)을 시킬까 봐 매일 밤잠을 설치며 복잡한 방어 코드를 짜야 하는 양날의 검입니다.