서론
개발자라면 한 번쯤은 특정 필터, 검색 요건을 충족하는 검색 API를 만들어 본 적이 있을 것이다.
조건이 단순하면 간단하지만, 조금만 필터가 복잡해져도 URL은 금방 지저분해진다. 중첩 조건, 범위 검색, 정렬, 페이지네이션, OR 조건 등 여러 가지 조건들이 붙으면 쿼리 스트링은 읽기도 어렵고 지저분해진다.

그래서 조건을 body나 operation payload에 싣는 POST나 GraphQL을 통해 이런 문제를 풀기도 한다. POST 메서드나 GraphQL의 query operation과 variables를 POST body에 담아 보내기도 한다.
POST /contacts/search HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"filter": {
"status": "active",
"tags": ["a", "b"],
"createdAt": { "gte": "2026-01-01" }
},
"sort": "-createdAt"
}
POST /graphql HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"query": "query Contacts($status: String!) { contacts(status: $status) { id username } }",
"variables": { "status": "active" }
}
두 방식 모두 복잡한 검색 조건을 표현하기에는 훨씬 편하다. 하지만 HTTP 의미론 관점에서는 찜찜한 지점이 남는다.
우리가 원하는 것은 서버 리소스를 생성하거나 변경하는 것이 아니다. 조건이 복잡해서 URL query string만으로 표현하기 어렵기 때문에 request body가 필요할 뿐이다. 그런데 HTTP method는 POST다.
POST가 항상 리소스 생성을 뜻하는 것은 아니지만, RFC 9110 기준으로 POST는 safe하지도, idempotent하지도 않은 메서드로 취급된다. 즉 애플리케이션 레이어에서는 읽기 요청이라고 알고 있어도, HTTP 레이어와 중간 인프라 입장에서는 이 요청이 읽기인지 쓰기인지 method만 보고 판단하기 어렵다.
HTTP Method의 safe와 idempotent에 대해 다룬 글이 있으니 필요하다면 참고하길 바란다.
HTTP Method의 멱등성(Idempotence) 이해하기
멱등성(Idempotency)멱등성이라는 용어부터 살펴보자. 멱등성은 수학에서 유래된 개념으로, 같은 작업을 여러 번 반복해도 결과가 달라지지 않는 성질을 뜻한다. 이 개념은 HTTP Method에서도 중요한
mag1c.tistory.com
POST, GraphQL의 문제
POST /search와 GraphQL POST는 실무적으로 좋은 해법이다. 복잡한 조건을 JSON으로 표현할 수 있고, typed client나 schema validation을 얹기도 쉽다. GraphQL은 한 걸음 더 나아가 클라이언트가 필요한 field shape까지 operation으로 표현할 수 있다.
하지만 HTTP 관점에서는 어떨까?
요청한 애플리케이션의 의미는 읽기이고, 조건 표현을 위해 request body가 필요하다. HTTP method는 POST라서 safe, idempotent의 의미가 method에 드러나지 않는다. 문제는 조건을 어디에 담을 것인가 만이 아니라, 그 요청이 HTTP 레이어에서 어떤 의미로 보이는가를 같이 생각해보아야 한다.
GraphQL도 마찬가지로 애플리케이션 레이어에서는 읽기라고 말하지만 HTTP method만 보면 그냥 POST로 보인다. 그래서 GraphQL 생태계에서는 persisted query 같은 패턴도 생겼다. 긴 query document를 매번 보내는 대신 hash를 URL에 싣거나 CDN cache key를 직접 조정하거나 POST body를 해석하는 애플리케이션 레벨 캐시를 둔다. 하지만 이런 방법들이 HTTP method의 의미 자체를 바꿔주지는 않는다.
그럼 GET에 body는?
그럼 의미론적으로 읽기 요청인 GET에 body를 실어 보내는 방법은 왜 안됐을까?
GET에서도 당연히 실어 보낼 수는 있다. 하지만 RFC 9110에서 요청 content에 대해 일반적으로 정의된 의미가 없다고 설명한다. 서버가 GET body를 검색 조건으로서 해석하고 사용해야 하는지, 무시하고 거부해야 하는지 표준으로 정해주지 않는다.

클라이언트, 서버, 프록시, CDN, 브라우저, 캐시는 모두 method를 보고 이 요청을 어떻게 다룰지 판단하는데, GET body에 자체 규칙을 얹으면 내가 관리하는 서버에서는 동작할 수 있지만, 중간 경로 전체가 같은 의미를 공유한다고 기대하기 어렵다.
HTTP QUERY Method
이런 간극을 다루기 위해 IETF에서는 2026년 6월 RFC 10008을 발행했다.
QUERY method는 IANA HTTP Method Registry에 등록됐고, 이제 HTTP 안에서 body를 가진 safe하고 idempotent한 요청을 표현할 공식적인 메서드가 생겼다.

QUERY /contacts HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"filter": {
"status": "active",
"tags": ["a", "b"],
"createdAt": { "gte": "2026-01-01" }
},
"sort": "-createdAt"
}
QUERY는 POST처럼 body를 실을 수 있지만, GET처럼 safe하고 idempotent한 query 요청을 표현할 수 있다.

이런 특성을 가진 QUERY라는 method의 의미는 단순 GET 요청에 body를 실을 수 있는 것보다는 더 많은 가치가 있을 것으로 보인다.
- 연결이 끊겼을 때 재시도를 고려할 수 있다.
- 캐시가 safe한 query response로 판단될 수 있다.
- gateway나 WAF 읽기 요청과 쓰기 요청을 더 명확하게 구분할 수 있다.
- observability 관점에서 method 기준으로 요청의 성격을 해석하기 쉬워진다. (기존 POST의 오용 감소)
QUERY, 사용해 볼 수 있을까?
QUERY가 표준화됐다고 해서 내일부터 모든 API에 바로 적용하기는 어렵다.
우선 중간 경로가 method를 알아야 한다. 오래된 proxy, WAF, CDN, gateway는 알 수 없는 method를 거부하거나 별도 allowlist로 관리할 수 있다. 서버 framework가 QUERY 라우팅을 지원하더라도 실제 인터넷 경로에서는 막힐 수 있다.
또한 브라우저의 CORS에서 QUERY는 safelisted method가 아니다. RFC 10008도 CORS를 구현하는 user agent에서 QUERY 요청은 preflight가 필요하다고 언급한다. 브라우저 클라이언트에서 쓸 때는 OPTIONS 처리와 Access-Control-Allow-Methods 설정까지 같이 봐야 한다.
그리고 캐시가 request content를 cache key에 포함해야 한다. 이걸 모르는 캐시는 QUERY response를 아예 저장하지 않거나, 저장하더라도 잘못 재사용할 위험이 있다. QUERY의 장점을 제대로 얻으려면 cache layer가 RFC 10008의 규칙을 구현해야 한다. RFC 10008은 QUERY request의 cache key가 request content와 관련 metadata를 포함해야 한다고 명시한다.
마지막으로 지원 여부를 발견하는 절차가 필요하다. RFC 10008은 Accept-Query response header를 정의한다. 서버는 어떤 media type의 query content를 받을 수 있는지 알릴 수 있다.
Accept-Query: "application/jsonpath", application/sql;charset="UTF-8"
또는 OPTIONS 응답의 Allow header로 QUERY 지원 여부를 드러낼 수 있다.
Allow: GET, QUERY, OPTIONS, HEAD
현실적인 도입 순서는 공용 브라우저 API보다 내부 API나 gateway-controlled API 쪽일 가능성이 높다. 클라이언트, 서버, proxy, cache 설정을 한 팀이 함께 통제할 수 있어야 QUERY의 의미와 이점을 끝까지 보존할 수 있기 때문이다.
마치며
QUERY는 POST이지만 GET으로 사용했던 기존의 레거시 API들을 대체할 수 있는 것처럼 보인다. 그렇다고 당장 기존 API를 바꿀 필요는 없다. POST는 여전히 실용적이며 모든 인프라가 POST를 알고 있고, framework와 client 지원도 안정적이다. 캐시가 꼭 필요하지 않거나, 애플리케이션 레벨에서 캐시/재시도/중복 방지를 이미 잘 처리하고 있다면 POST 검색 API를 유지하는 편이 더 현실적일 수 있다.
다만 RFC 10008이 의미 있는 이유는, 우리가 그동안 관습으로 처리하던 요구사항에 HTTP 차원의 이름을 붙였다는 데 있다. GET은 복잡한 request body를 표준 의미로 다룰 수 없으며, POST는 safe, idempotent하지 않다. 그 문제 정의를 표준으로 정의했다는 것에 의미가 있다고 본다.
그래서, POST지만 GET으로 사용되던 레거시를 완전 대체하기보다는 아직은 생태계 지원을 확인하면서 조심스럽게 봐야 하지만, 검색 API와 GraphQL, 분석 API를 설계할 때 앞으로 꽤 자주 언급되고, 고려할 대상으로 보이지 않을까? 싶다.