GraphQL은 클라이언트가 필요한 필드를 정확히 명시해 요청하는 쿼리 언어입니다. REST가 여러 리소스마다 개별 URL을 두는 것과 달리, GraphQL은 보통 단 하나의 엔드포인트(관례상 /graphql)로 모든 요청을 처리합니다. GraphQL 자체는 전송 프로토콜을 규정하지 않지만, 사실상 HTTP 위에서 운용되며 그 방식이 GraphQL over HTTP 명세로 정리되고 있습니다.
단일 엔드포인트와 POST
가장 일반적인 방식은 하나의 URL로 POST 하는 것입니다. 요청 본문은 세 개의 표준 키를 갖는 JSON입니다: 실행할 문서인 query, 변수 값 묶음인 variables, 그리고 문서에 여러 오퍼레이션이 있을 때 어느 것을 실행할지 고르는 operationName.
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/graphql-response+json
{"query":"query GetUser($id: ID!){ user(id:$id){ name email } }",
"variables":{"id":"u_92311"},
"operationName":"GetUser"}
쿼리에 GET 쓰기
읽기 전용 쿼리는 GET 으로도 보낼 수 있습니다. query·variables(URL 인코딩된 JSON)·operationName을 쿼리 문자열에 실으면 됩니다. GET을 쓰면 요청이 URL로 표현되어 HTTP 캐시·CDN이 응답을 캐시할 수 있고, 브라우저 주소창으로 공유·북마크가 가능해집니다. 다만 상태를 바꾸는 뮤테이션(mutation)은 부작용이 있으므로 반드시 POST를 써야 하고, URL 길이 제한 때문에 큰 쿼리는 GET에 담기 어렵습니다.
GET /graphql?query=query%7Buser(id%3A%22u_92311%22)%7Bname%7D%7D HTTP/1.1
Host: api.example.com
Accept: application/graphql-response+json
콘텐츠 타입과 상태 코드 관례
GraphQL over HTTP 명세는 응답에 application/graphql-response+json 미디어 타입을 도입했습니다. 기존 관행인 application/json도 널리 쓰이지만, 새 타입은 '이 본문은 GraphQL 규격에 따른 응답'임을 명확히 해 클라이언트가 오류 처리를 더 정확히 하게 해 줍니다. 요청은 Accept 헤더로 원하는 타입을 협상합니다.
여기서 가장 헷갈리는 지점이 상태 코드입니다. 전통적으로 GraphQL 서버는 쿼리 실행 중 필드 오류가 나도 HTTP 200 OK 를 돌려주고, 실제 오류는 본문의 errors 배열에 담았습니다. HTTP 계층에서는 요청·응답이 정상적으로 오갔기 때문입니다.
{
"data": { "user": null },
"errors": [
{ "message": "User not found",
"path": ["user"],
"extensions": { "code": "NOT_FOUND" } }
]
}
- 부분 성공:
data와errors가 동시에 존재할 수 있습니다. 일부 필드는 성공하고 일부만 실패한 경우입니다. - 200 안의 오류: 그래서 클라이언트는 HTTP 상태만 보지 말고 반드시
errors배열을 확인해야 합니다. - 새 관례:
application/graphql-response+json을 쓰면, 요청 자체가 잘못돼 실행조차 못 한 경우(문법 오류 등)에는400을, 정상 실행됐으나 필드 오류가 난 경우에는200을 쓰도록 구분이 권장됩니다. - 인증 실패:
401/403처럼 HTTP 차원의 오류는 여전히 해당 상태 코드로 반환하는 것이 자연스럽습니다.
캐싱 난제와 배칭
GraphQL의 최대 약점 중 하나가 HTTP 캐싱입니다. 모든 요청이 같은 URL로 가는 POST이고 쿼리 내용이 본문에 있으므로, URL을 키로 삼는 CDN·브라우저 캐시가 응답을 구분하지 못합니다. 이 때문에 (1) 쿼리를 GET으로 보내 URL을 캐시 키로 활용하거나, (2) 서버·클라이언트 레벨의 정교한 캐시(정규화 캐시)를 쓰거나, (3) persisted query(쿼리를 해시로 미리 등록해 짧은 식별자만 전송)로 URL을 캐시 친화적으로 만드는 기법을 씁니다. 한편 여러 오퍼레이션을 한 요청에 묶어 보내는 배칭(batching) 은 왕복을 줄여 주지만, 개별 응답의 캐싱·오류 격리·타임아웃 처리를 복잡하게 만들므로 트레이드오프를 따져 도입해야 합니다.