QUERY が追加された、という話を見かけた。
最初に聞くと、少しややこしい。
「queryって、URLの?q=... のことでは?」と思ってしまうが、今回の話はそれとは違う。
GET や POST と同じような、HTTPメソッドとしての QUERY だ。
QUERY /orders HTTP/1.1
Content-Type: application/json
{
"status": ["paid", "shipped"],
"createdAt": {
"from": "2026-07-01",
"to": "2026-07-31"
}
}
これは、ざっくり言うと Body付きで安全な問い合わせをするためのHTTPメソッド だ。
2026年6月に RFC 10008 として標準化されている。
今まではGETかPOSTで頑張っていた
これまで、検索APIを作るときはだいたいGET を使っていた。
GET /orders?status=paid&from=2026-07-01&to=2026-07-31
このくらいなら、特に問題ない。
GET は読み取り用のメソッドとして自然だし、URLとしても共有しやすい。
ブラウザでも扱いやすく、キャッシュとの相性もいい。
W3C TAGの古い文書でも、安全な操作、つまりサーバー状態を変更しないような問い合わせには GET を使うべき、という考え方が示されている。
ただ、現実のAPIでは検索条件がどんどん複雑になる。
例えば、複数条件のフィルタ、ネストした条件、全文検索、集計条件、ソート条件などを全部URLに詰め込もうとすると、かなり無理が出てくる。
GET /orders?filters=%7B%22status%22%3A%5B%22paid%22%2C%22shipped%22%5D%2C...
こうなると、人間が読むのもつらい。 URLも長くなるし、エンコードも面倒になる。
それに、URLはログやブラウザ履歴、アクセス解析、リファラなどに残りやすい。 検索条件の中にユーザー入力や少しセンシティブな情報が含まれる場合、URLに載せるのは気持ち悪いこともある。
そこで実務では、こういうAPIをよく作っていた。
POST /orders/search
Content-Type: application/json
{
"status": ["paid", "shipped"],
"createdAt": {
"from": "2026-07-01",
"to": "2026-07-31"
}
}
これはかなり現実的だ。
BodyにJSONを入れられるので、複雑な検索条件を表現しやすい。 APIとしても実装しやすい。
ただ、HTTPの意味として見ると少しズレがある。
検索なのにPOSTでいいのか問題
POST /orders/search は、実務ではよくある。
ただし、POST は本来「何かを作る」「処理する」「状態を変えるかもしれない」メソッドとして使われることが多い。
もちろん、POSTだから必ず状態変更する、というわけではない。 検索用にPOSTを使っても、サーバー側で状態変更しなければ実害はない。
ただ、HTTPメソッドだけを見たときに、クライアントやプロキシやキャッシュは、それが安全な読み取りなのか判断しにくい。
例えば、ネットワークが途中で切れたときに自動でリトライしてよいのか。 キャッシュしてよいのか。 再実行しても副作用がないのか。
GET なら「基本的には安全な読み取りだな」と判断できる。
でも POST だと、サーバー固有の事情を知らない限り判断しづらい。
つまり、POST /search は便利だけど、HTTPの意味としては少し曖昧だった。
この曖昧さを埋めるために出てきたのが QUERY だ。
QUERYは「Body付きの安全な問い合わせ」
RFC 10008では、QUERY はリクエスト対象に対して、リクエストに含まれる内容を safe かつ idempotent に処理し、その結果を返すメソッドとして定義されている。
safeというのは、ざっくり言えば「サーバーの状態を変更しない」という意味。 idempotentは、同じリクエストを何回実行しても副作用の面で同じ性質になる、という意味だ。
なのでQUERY は、POSTのようにBodyを送れるが、意味としては読み取り・問い合わせに寄っている。
QUERY /orders
Content-Type: application/json
{
"status": ["paid", "shipped"],
"createdAt": {
"from": "2026-07-01",
"to": "2026-07-31"
}
}
これはかなり素直に読める。
/orders に対して、Bodyで指定した条件で問い合わせている。
でも、注文を作ったり更新したりしているわけではない。
これまで POST /orders/search と書いていたものを、HTTPメソッドとしてもう少し正確に表現できるようになった、と考えると分かりやすい。
もともとの問題意識は「safe method with body」
面白いのは、RFCになる前のドラフト名がsafe-method-w-body だったことだ。
つまり、最初から問題意識としてはかなり分かりやすい。
Bodyを持てるsafeなHTTPメソッドが欲しい
という話だった。
GETはsafeだが、複雑な条件をBodyに入れる用途には向いていない。 POSTはBodyを持てるが、safeとは限らない。
この2つの間にあるユースケースが、実務ではずっと存在していた。
GET = safeだが、複雑なBody付き問い合わせには向かない
POST = Bodyを送れるが、safeな問い合わせだとは限らない
QUERY = Bodyを送れて、かつsafeな問い合わせとして表現できる
こう整理すると、QUERY が何を埋めようとしているのか分かりやすい。
GET with Bodyではダメだったのか
ここで自然に思うのが、GET にBodyを付ければよかったのでは、ということだ。
GET /orders HTTP/1.1
Content-Type: application/json
{
"status": ["paid"]
}
見た目だけなら、これでもよさそうに見える。
ただ、GETのBodyは歴史的に扱いが不安定だ。 実務的にはもっと単純で、サーバー、プロキシ、ライブラリ、ブラウザAPIなどが期待通りに扱ってくれるとは限らない。
無視されるかもしれない。 途中のミドルウェアで落とされるかもしれない。 クライアントライブラリがそもそも送れないかもしれない。
また、キャッシュの観点でも難しい。 GETは基本的にURIを中心に扱われるため、同じURIでBodyだけ違うリクエストをどう扱うのか、実装によってずれが出やすい。
つまり、GETの意味を無理に広げるよりも、Bodyを持てる安全な問い合わせメソッドを新しく定義した方が分かりやすい。
それがQUERY という整理だと思う。
なぜSEARCHではなくQUERYなのか
名前としてはSEARCH でもよさそうに見える。
実際、検索APIなら SEARCH /orders の方が直感的に見えるかもしれない。
ただ、HTTPにはWebDAV由来の SEARCH など、既存のメソッドもある。
また、今回やりたいことは単なる検索だけではなく、対象リソースに対してBody付きの問い合わせを行うことだ。
そう考えると、SEARCH よりも QUERY の方が広い。
検索だけでなく、集計、変換、絞り込み、問い合わせ言語の実行なども含められる。
例えば、以下のようなものもQUERY の範囲に入りうる。
QUERY /analytics
Content-Type: application/json
{
"groupBy": "month",
"metrics": ["sales", "orders"],
"where": {
"country": "JP"
}
}
これは「検索」というより「問い合わせ」に近い。
その意味では、QUERY という名前はけっこう自然だ。
Accept-Queryというヘッダーもある
QUERY には Accept-Query というレスポンスヘッダーも定義されている。
これは、サーバーが「このリソースは、この形式のQUERYを受け付けます」と示すためのものだ。
例えば、以下のようなイメージになる。
HTTP/1.1 200 OK
Accept-Query: application/json
クライアントはこれを見ることで、このリソースに対して application/json のBodyで QUERY を送れることが分かる。
もちろん、これがどのくらい実務で使われるかは、今後の対応状況次第だと思う。 ただ、仕様としては「QUERYをどう発見させるか」も考えられている。
キャッシュはできるが、GETよりは難しい
QUERY はsafeでidempotentなので、キャッシュとの相性も意識されている。
ただし、GETのように単純ではない。
GETの場合、多くのキャッシュはURIを中心にキャッシュキーを作る。 一方でQUERY はBodyに問い合わせ条件が入る。
つまり、同じ /orders に対する QUERY でも、Bodyが違えば別の問い合わせになる。
QUERY /orders
Content-Type: application/json
{ "status": "paid" }
QUERY /orders
Content-Type: application/json
{ "status": "cancelled" }
この2つは、同じURIでも違う結果になるはずだ。
そのため、QUERY のキャッシュでは、URIだけでなくリクエストBodyや関連するメタデータも考慮する必要がある。
仕様としてキャッシュ可能ではあるが、実装はGETより難しい。
ここは、実務で使うときに注意が必要だと思う。
CORSではプリフライトが必要になる
ブラウザからQUERY を使う場合、CORSのプリフライトも考える必要がある。
GET や POST の一部のような、いわゆるシンプルリクエストとして扱われるわけではないため、別オリジンに送る場合は OPTIONS リクエストが先に飛ぶ。
つまり、API側で QUERY を許可するだけでなく、CORS設定でも QUERY メソッドを許可する必要がある。
Access-Control-Allow-Methods: GET, POST, QUERY, OPTIONS
このあたりも、QUERY を実務で使う場合の小さくないハードルになる。
実務ではすぐ使うべきか
では、明日から全部QUERY に変えるべきかというと、そこは慎重でいいと思う。
仕様としては標準化された。 ただ、実務ではHTTPメソッドそのものより、周辺の対応状況がかなり重要になる。
例えば、以下のようなものがQUERY を問題なく通してくれる必要がある。
- ブラウザ
- fetch / axios などのクライアント
- API Gateway
- CDN
- WAF
- リバースプロキシ
- Webフレームワーク
- OpenAPIなどのドキュメント生成
- ログ基盤
- 監視ツール
HTTPメソッドとして正しくても、途中のどこかで未知のメソッドとして弾かれると使いづらい。
特に公開APIや、いろいろなクライアントが叩くAPIでは、まだPOST /search の方が無難な場面は多いと思う。
じゃあQUERYの価値は何か
QUERY の価値は、今すぐPOST検索を置き換えることよりも、これまで曖昧だった設計に名前がついたこと だと思う。
これまでの検索APIには、だいたい3つの選択肢があった。
GET /orders?status=paid
シンプルな検索ならこれでいい。
POST /orders/search
Content-Type: application/json
{
"status": "paid"
}
複雑な検索なら実務上はこれが多かった。
QUERY /orders
Content-Type: application/json
{
"status": "paid"
}
そして、これからはこの選択肢もある。
整理すると、こんな感じになる。
| やりたいこと | 向いているメソッド |
|---|---|
| URLで表現できる単純な取得 | GET |
| Body付きの安全な問い合わせ | QUERY |
| 作成・更新・副作用のある処理 | POST |
この整理はかなり分かりやすい。
特に、検索条件が複雑だけど、意味としては明らかに読み取りであるAPIにはQUERY が合う。
REST API設計として見ると面白い
REST APIの設計では、よく「これはリソースなのか」「これは操作なのか」という話になる。
例えば、以下のようなAPIがある。
POST /orders/search
これは実務的には自然だが、少しRPCっぽくも見える。
search という操作名がパスに入っているからだ。
一方で、QUERY を使うとこう書ける。
QUERY /orders
対象はあくまで /orders。
その注文リソース群に対して、問い合わせをしている。
この方が、HTTPメソッドとリソースの役割分担としてはきれいに見える。
もちろん、きれいだから常に良いというわけではない。 実務では対応状況やチームの理解の方が大事なことも多い。
ただ、設計としてはかなり納得感がある。
POST検索は間違いだったのか
では、これまでのPOST /search は間違いだったのか。
個人的には、そうは思わない。
むしろ、現実的な制約の中ではかなり妥当な設計だったと思う。
GETではURLが長くなりすぎる。 GET with Bodyは実装上の不安がある。 複雑な検索条件はJSONで送りたい。
そうなると、POSTを使うしかなかった。
QUERY は、POST検索を否定するものというより、POST検索が担っていた役割の一部を、HTTPとして正式に表現できるようにしたもの と見るのがよさそうだ。
今まではPOSTで代用していた。 これからは、意味としてはQUERYの方が合う場面が出てくる。
そのくらいの温度感がちょうどいいと思う。
まとめ
HTTPQUERY メソッドは、GETとPOSTの間にあった微妙な穴を埋めるためのものだ。
GETは、URLで表現できるシンプルな取得に向いている。 POSTは、作成や変更、副作用のある処理に向いている。 でも、複雑な検索条件をBodyで送りたいだけの読み取りAPIは、その中間にあった。
これまでは、その中間をPOST /search でなんとかしていた。
QUERY は、その中間に名前と意味を与えるメソッドだ。
ただし、標準化されたからといって、すぐに全APIで使えるとは限らない。 CDN、WAF、API Gateway、フレームワーク、クライアントライブラリなど、周辺の対応状況を見る必要がある。
なので、現時点ではこんな見方がよさそうだ。
単純な検索なら GET
複雑な検索で互換性重視なら POST /search
HTTPの意味を正確に表したいなら QUERY
QUERY は、今すぐ主流になるというより、これまで実務でなんとなくやっていた「Body付き検索API」に、HTTPとしての正式な居場所を作ったものだと思う。