Skip to content

필사 모드: The HTTP QUERY Method — A New Verb for an Old REST Dilemma

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

Recently an article about the HTTP QUERY method went viral on GeekNews (news.hada.io) and Hacker News. In particular, the kreya.app blog post titled "HTTP QUERY method" climbed to the top of GeekNews, sparking reactions among developers along the lines of "are we finally getting a verb to design search APIs properly?"

Anyone who has ever seriously designed a REST API has faced the same dilemma: **how do you send complex search criteria to the server?** When filters are simple, a GET query string is enough. But once you have nested filters mixed with sorting, pagination, and aggregation conditions, the story changes.

Use GET, and you cannot send a request body, and you run into URL length limits. Use POST, and you can send a body, but the semantics are entirely wrong for "search." POST is neither safe, nor idempotent, nor cacheable.

In this article we examine the essence of this old dilemma, look at how the HTTP QUERY method that the IETF is standardizing resolves the problem, and discuss what you should consider in practice right now.

1. The Heart of the Problem — The Gap Between GET and POST

1.1 The Limits of GET

GET is the most basic HTTP verb and is used to read resources. Its core properties are as follows.

- **Safe**: It does not change server state.

- **Idempotent**: Calling it multiple times yields the same result.

- **Cacheable**: Its responses can be cached.

The problem is that GET semantically does not carry a body. RFC 9110 states that sending a body with a GET request "has no defined semantics" and that some implementations may reject it. In effect, a GET body is unusable.

So all complex search criteria must be packed into the URL query string. This creates three practical problems.

| Problem area | Concrete symptom |

| ---------------- | ---------------------------------------------------------------------- |

| URL length limit | Limits differ across browsers, proxies, and servers; usually 2KB to 8KB |

| Encoding burden | Expressing nested structures in a query string makes serialization ugly |

| Log exposure | Search terms and criteria leak into access logs, proxy logs, history |

The URL length limit in particular is not a fixed standard value but is implementation dependent. Some older proxies or servers may impose shorter limits, so long search criteria get unpredictably truncated or rejected.

Here is an example of expressing a nested structure in a query string.

GET /products?filter[category]=shoes&filter[price][gte]=100&filter[price][lte]=200&sort[]=-rating&sort[]=price&page[number]=2&page[size]=20

This approach has no standard, so parsing rules differ across frameworks. The same intent expressed in JSON is far clearer.

{

"filter": {

"category": "shoes",

"price": { "gte": 100, "lte": 200 }

},

"sort": ["-rating", "price"],

"page": { "number": 2, "size": 20 }

}

The JSON body is obviously more readable and structured. But you cannot send this body with GET.

1.2 POST's Semantic Mismatch

So most developers flee to POST. POST lets you send a body freely, so you just load that JSON and send it.

POST /products/search HTTP/1.1

Host: api.example.com

Content-Type: application/json

{

"filter": { "category": "shoes", "price": { "gte": 100, "lte": 200 } },

"sort": ["-rating", "price"],

"page": { "number": 2, "size": 20 }

}

It works. But semantically, POST is entirely the wrong verb for search. Look at POST's standard properties.

| Property | GET | POST | Needed for search |

| ----------- | ---- | ---- | ----------------- |

| Safety | Yes | No | Yes |

| Idempotency | Yes | No | Yes |

| Cacheable | Yes | No | Useful |

Search is fundamentally a **read operation**. It should not change server state (safe), should return the same result no matter how many times you call it (idempotent), and should let you cache the response for identical criteria (cacheable). Yet POST guarantees none of these three.

Using POST for search produces these side effects.

- Intermediate caches (CDNs, reverse proxies) cannot cache the response.

- Client libraries and proxies cannot safely retry the request, because POST retries are assumed to risk side effects.

- Monitoring and API gateways classify the request as a "write," so they cannot apply read-only policies (such as routing to read replicas).

In short, GET lacks a body and POST has the wrong meaning. This gap is exactly where the QUERY method belongs.

2. What Is the HTTP QUERY Method

2.1 A One-Line Definition

The HTTP QUERY method is a new HTTP verb that **can carry a request body while being safe and idempotent**. Put simply, it is "a GET that can send a body."

The IETF HTTP Working Group (httpbis) is standardizing it as an Internet Draft named "HTTP QUERY Method" (draft-ietf-httpbis-safe-method-w-body). The phrase "safe-method-with-body" embedded in the draft name precisely summarizes the essence of this verb.

2.2 Core Properties

The semantics of the QUERY method come down to these three points.

| Property | QUERY | Meaning |

| ------------ | ----- | ---------------------------------------------------- |

| Safety | Yes | A read operation that does not change server state |

| Idempotency | Yes | Guarantees the same result no matter how often called |

| Body allowed | Yes | Structured search criteria can be placed in the body |

On top of this, responses can be cached when the right conditions are met. This is the key point. QUERY carries a body yet does not give up cacheability.

A side-by-side comparison of the three verbs looks like this.

| Item | GET | POST | QUERY |

| ----------- | --------------- | ------------- | -------------------- |

| Request body | Practically no | Yes | Yes |

| Safety | Yes | No | Yes |

| Idempotency | Yes | No | Yes |

| Cacheable | Yes | No | Yes (conditional) |

| Fit for search | Body limited | Wrong meaning | Meaning fits exactly |

2.3 The Shape of Request and Response

A QUERY request is safe like GET but carries a body like POST. Expressing the earlier product search as QUERY looks like this.

QUERY /products HTTP/1.1

Host: api.example.com

Content-Type: application/json

Accept: application/json

{

"filter": {

"category": "shoes",

"price": { "gte": 100, "lte": 200 }

},

"sort": ["-rating", "price"],

"page": { "number": 2, "size": 20 }

}

The server returns ordinary search results.

HTTP/1.1 200 OK

Content-Type: application/json

{

"total": 142,

"results": [

{ "id": "p-1001", "name": "Trail Runner", "price": 159 },

{ "id": "p-1002", "name": "City Walker", "price": 129 }

]

}

Compared to GET, only the verb changed and the meaning stays "read." Compared to POST, the body is sent the same way but with the added semantic guarantees of safety and idempotency.

3. The Cacheability of QUERY Responses

3.1 The Cache Key Problem

When caching a GET response, the cache key is usually the URL. The assumption is that the same URL means the same response. But with QUERY, the search criteria live in the body. That means two requests with the same URL but different bodies can return different results.

So to cache a QUERY response you must **include the body in the cache key**. The HTTP QUERY draft discusses mechanisms for this. The core idea is that the server provides canonical cache key information in the response, and intermediate caches make use of it.

3.2 Cache Handling via Content-Location

One approach is for the server to tell the client, via the `Content-Location` header, the "canonical URL corresponding to this query." Then for identical queries, that URL can serve as the cache key.

QUERY /products HTTP/1.1

Host: api.example.com

Content-Type: application/json

{ "filter": { "category": "shoes" }, "page": { "size": 20 } }

The response presents the canonical location.

HTTP/1.1 200 OK

Content-Type: application/json

Content-Location: /products?category=shoes&size=20

Cache-Control: max-age=60

{ "total": 88, "results": [] }

This lets you have both the expressiveness of a body-based search and the caching benefits of a GET-based approach. That said, this area is still under active discussion in the draft, so implementations should follow the recommendations of the latest draft.

3.3 Cache Behavior Comparison

| Scenario | GET | POST | QUERY |

| --------------------- | --------------- | ----------------- | --------------------------- |

| Browser cache | Default | Not cached | Possible (needs work) |

| CDN / reverse proxy | Broadly supported | Usually unsupported | Gradual support expected |

| Cache key | URL | Not cached | URL plus body |

4. Workarounds Used So Far

Until QUERY is standardized, the industry has dealt with this problem through various workarounds. Examining each one's pros and cons makes it even clearer why QUERY is needed.

4.1 Dedicated Search POST Endpoints

This is the most common approach: creating a separate POST endpoint for search. Elasticsearch's search API is a representative example.

POST /products/_search HTTP/1.1

Host: search.example.com

Content-Type: application/json

{

"query": {

"bool": {

"must": [{ "match": { "category": "shoes" } }],

"filter": [{ "range": { "price": { "gte": 100, "lte": 200 } } }]

}

},

"sort": [{ "rating": "desc" }],

"from": 20,

"size": 20

}

Elasticsearch allows both GET and POST, but POST became the de facto standard practice for safely sending a body. It works, but it inherits all the semantic limits seen earlier. Search gets classified as a "write" and is not cached.

An action path like `_search` appended to the URL also conflicts with RESTful design principles. REST favors noun-based resources, but `_search` is plainly a verb.

4.2 Giant GET Query Strings

Another approach is cramming all conditions into a GET query string. It works fine for simple searches, but the more complex the criteria, the more uncontrollably long the URL grows.

GET /products?q=shoes&min=100&max=200&sort=-rating,price&fields=id,name,price&include=reviews&filter.brand.in=nike,adidas&page=2&size=20

URL length limits, encoding hell, and log exposure all pile up. Worse, there is no standard for expressing complex logic like nested structures or OR conditions in a query string, so compatibility issues never stop.

4.3 GraphQL

GraphQL sidesteps the problem with a different approach. It POSTs every request to a single endpoint and describes the desired data with a query language.

POST /graphql HTTP/1.1

Host: api.example.com

Content-Type: application/json

{ "query": "query($cat: String!) { products(category: $cat) { id name price } }", "variables": { "cat": "shoes" } }

GraphQL is powerful, but at the HTTP level everything is still POST. It is hard to gain the benefits of HTTP caching and intermediate infrastructure, and it requires a separate stack and learning curve. For an ordinary REST service where GraphQL is not a fit, it can be overkill.

4.4 Workaround Comparison

| Approach | Uses body | Semantic accuracy | Cacheable | Standardization |

| --------------------- | --------- | ----------------- | --------- | ------------------- |

| POST search endpoint | Yes | Low | Low | Conventional |

| Giant GET | No | Medium | High | Non-standard serial |

| GraphQL | Yes | Separate model | Low | Its own standard |

| QUERY | Yes | High | High | In standardization |

5. Practical Adoption — What Can You Do Now

5.1 Sending QUERY from the Client

Most HTTP clients accept an arbitrary method string. With curl you can send it as follows.

curl -X QUERY https://api.example.com/products \

-H "Content-Type: application/json" \

-H "Accept: application/json" \

-d '{ "filter": { "category": "shoes" }, "page": { "size": 20 } }'

JavaScript's fetch API also supports arbitrary methods.

fetch("https://api.example.com/products", {

method: "QUERY",

headers: { "Content-Type": "application/json" },

body: JSON.stringify({ filter: { category: "shoes" } })

})

Note that some environments may block methods not in the standard, so per-environment client verification is needed.

5.2 Receiving QUERY on the Server

The key question is whether your server framework recognizes the QUERY method in routing. Before the standard is finalized, many frameworks do not yet support it as a first-class citizen. As a stopgap, you register custom method routing.

Server routing pseudocode

route(method="QUERY", path="/products", handler=searchProducts)

function searchProducts(request):

criteria = parseJson(request.body)

results = repository.search(criteria)

response.header("Cache-Control", "max-age=60")

return json(results)

5.3 A Gradual Rollout Strategy

A full immediate rollout is risky. A staged approach is recommended.

| Stage | Action |

| ------- | ------------------------------------------------------------------ |

| Stage 1 | Try it first in internal service-to-service traffic to verify infra |

| Stage 2 | Use dual routing accepting both QUERY and POST for compatibility |

| Stage 3 | Design client SDKs to fall back to POST where QUERY is unsupported |

| Stage 4 | Monitor whether proxies and gateways pass QUERY through |

Stage 2 dual routing in particular is a realistic starting point. By mapping both QUERY and POST to the same handler, clients that support QUERY get more accurate semantics, while those that do not keep working with the existing POST.

6. Pitfalls and a Critical View

6.1 Intermediaries Are the Biggest Variable

QUERY's biggest practical barrier is **intermediaries**. The internet is full of proxies, load balancers, CDNs, firewalls, and API gateways, and they tend to act conservatively when they encounter an HTTP method they do not recognize.

- Some proxies reject unknown methods outright (405 or 501).

- Some fail to anticipate a safe method with a body and may drop the body.

- Security appliances may treat a non-standard method as a potential attack and block it.

Until the standard is finalized as an RFC and major infrastructure vendors implement it, it is hard to use QUERY reliably across paths that cross the public internet. So early adoption realistically starts on a controllable internal network.

6.2 Immature Framework and Tooling Support

At present, few web frameworks support QUERY as a first-class citizen. Routing, middleware, body parsing, and OpenAPI documentation tools all need updates to recognize the new method. This ecosystem transition takes time.

6.3 The Skepticism of "Do We Really Need a New Verb"

There are critical voices too: "POST works fine, so do we really need a new method?" Indeed, countless search APIs have run on POST perfectly well for years.

The counterargument is clear. Semantics are not mere formality but a **contract for infrastructure to behave correctly**. If you can declare at the HTTP level that a search is safe and idempotent, then caching, retries, routing, and monitoring all work correctly and automatically. POST hides this information; QUERY reveals it. Over the long run, that difference is not small.

6.4 The Caching Spec Is Not Finalized

The body-based caching seen earlier is appealing but is still an area without full consensus. Cache key normalization, the use of `Content-Location`, and intermediate cache behavior may change as the draft evolves. If you adopt QUERY treating caching as a core value, you should be conservative until the spec is finalized.

6.5 Pitfall Summary

| Pitfall | Mitigation |

| -------------------------- | --------------------------------------------------- |

| Intermediary incompatibility | Internal-first adoption, pre-verify path infra |

| No framework support | Register custom routing, secure POST fallback |

| Unfinalized cache spec | Be conservative with caching, track latest draft |

| Skepticism and org resistance | Persuade with data and cases on semantic benefits |

7. A Comprehensive Comparison Diagram

Simplifying how the three verbs handle a search request looks like this.

[Client] --- search request ---> [HTTP method] ---> [Server]

GET : no body, all criteria exposed in the URL

safe / idempotent / cacheable

but length limits and limited expressiveness

POST : has body, fully expressive

but not safe / not idempotent / not cacheable

infrastructure mistakes it for a write

QUERY : has body, fully expressive

safe / idempotent / cacheable (conditional)

satisfies semantics and expressiveness at once

This picture summarizes QUERY's reason for being on a single page. QUERY packs GET's semantic accuracy and POST's expressiveness into one verb.

Closing

The HTTP QUERY method is not a flashy new technology. It is closer to belatedly filling a blank that HTTP left empty from the start. It is a simple idea, "a safe method with a body," yet that simplicity resolves the compromise REST API designers have endured for years.

Of course, you cannot switch every search API to QUERY right now. The standard must be finalized as an RFC, proxies and frameworks must follow, and the caching spec must be wrapped up. But the direction is clear. The old dilemma of agonizing between GET's limits and POST's mismatch finally has a semantically correct verb.

What you can do now is clear. Track the progress of the IETF draft, experiment in a small way on internal services, and secure backward compatibility with dual routing. Then the moment the standard is finalized, you can switch over naturally. We are watching, in real time, a new HTTP verb taking its place.

References

- [GeekNews (news.hada.io)](https://news.hada.io/)

- [Hacker News](https://news.ycombinator.com/)

- [IETF Datatracker: HTTP QUERY Method (draft-ietf-httpbis-safe-method-w-body)](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/)

- [kreya.app: The HTTP QUERY method](https://kreya.app/blog/http-query-method/)

- [MDN: HTTP request methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)

- [RFC 9110: HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110)

- [HTTP Working Group (httpwg.org)](https://httpwg.org/)

- [Elasticsearch Search API documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html)

- [GraphQL official site](https://graphql.org/)

현재 단락 (1/209)

Recently an article about the HTTP QUERY method went viral on GeekNews (news.hada.io) and Hacker New...

작성 글자: 0원문 글자: 15,302작성 단락: 0/209