{"id":184,"date":"2026-07-26T11:16:00","date_gmt":"2026-07-26T11:16:00","guid":{"rendered":"https:\/\/softcrony.com\/blog\/?p=184"},"modified":"2026-07-26T11:16:00","modified_gmt":"2026-07-26T11:16:00","slug":"http-query-method-rfc-10008-explained","status":"publish","type":"post","link":"https:\/\/softcrony.com\/blog\/http-query-method-rfc-10008-explained\/","title":{"rendered":"HTTP QUERY Method Explained: The New RFC 10008 Standard Every API Developer Needs to Know"},"content":{"rendered":"<p>HTTP has had the same core methods since the 1990s \u2014 GET, POST, PUT, PATCH, DELETE. They&#8217;ve served the web well. But one scenario has always been awkward: complex searches that need a request body.<\/p>\n<p>RFC 10008, published in 2026, formalizes the HTTP QUERY method \u2014 a new HTTP verb specifically designed for safe, idempotent requests that carry a body. Here&#8217;s what it is, why it exists, and what it means for your API design.<\/p>\n<h2>The Problem QUERY Solves<\/h2>\n<p>Consider a complex search request. You want to search for orders that:<\/p>\n<ul>\n<li>Were placed between July 1\u201325, 2026<\/li>\n<li>Have status &#8220;pending&#8221; or &#8220;processing&#8221;<\/li>\n<li>Are from clients in Madhya Pradesh or Maharashtra<\/li>\n<li>Have a total value over \u20b950,000<\/li>\n<li>Include specific product categories<\/li>\n<li>Are assigned to specific sales representatives<\/li>\n<\/ul>\n<p>How do you send this in a GET request? You encode it as query parameters:<\/p>\n<pre><code>GET \/api\/orders?date_from=2026-07-01&date_to=2026-07-25&status[]=pending&status[]=processing&states[]=MP&states[]=MH&min_value=50000&categories[]=electronics&categories[]=furniture&reps[]=user_42&reps[]=user_67\r\n\r\n# This URL is 250+ characters and becomes completely unreadable\r\n# Some proxies and servers truncate URLs at 2048 characters\r\n# Complex nested filters become nearly impossible<\/code><\/pre>\n<p>The alternative \u2014 use POST:<\/p>\n<pre><code>POST \/api\/orders\/search\r\nContent-Type: application\/json\r\n\r\n{\r\n  \"filters\": {\r\n    \"date_range\": { \"from\": \"2026-07-01\", \"to\": \"2026-07-25\" },\r\n    \"status\": [\"pending\", \"processing\"],\r\n    \"client_states\": [\"MP\", \"MH\"],\r\n    \"min_value\": 50000,\r\n    \"categories\": [\"electronics\", \"furniture\"],\r\n    \"assigned_reps\": [\"user_42\", \"user_67\"]\r\n  }\r\n}<\/code><\/pre>\n<p>This works but it&#8217;s semantically wrong. POST means &#8220;create something&#8221; or &#8220;perform an action with side effects.&#8221; A search has no side effects \u2014 it&#8217;s a read-only operation. Using POST for reads violates REST semantics, confuses caching layers, and creates problems for documentation tools.<\/p>\n<p>This is the gap HTTP QUERY fills.<\/p>\n<h2>What RFC 10008 Defines<\/h2>\n<p>The HTTP QUERY method has these characteristics:<\/p>\n<ul>\n<li><strong>Safe:<\/strong> like GET \u2014 it doesn&#8217;t modify server state<\/li>\n<li><strong>Idempotent:<\/strong> like GET \u2014 calling it multiple times returns the same result<\/li>\n<li><strong>Has a request body:<\/strong> unlike GET \u2014 it can carry a structured query payload<\/li>\n<li><strong>Cacheable:<\/strong> responses can be cached like GET responses<\/li>\n<li><strong>Not a side-effect operation:<\/strong> unlike POST \u2014 it&#8217;s purely for retrieving data<\/li>\n<\/ul>\n<pre><code>QUERY \/api\/orders HTTP\/1.1\r\nHost: api.softcrony.com\r\nContent-Type: application\/json\r\nAccept: application\/json\r\n\r\n{\r\n  \"filters\": {\r\n    \"date_range\": { \"from\": \"2026-07-01\", \"to\": \"2026-07-25\" },\r\n    \"status\": [\"pending\", \"processing\"],\r\n    \"client_states\": [\"MP\", \"MH\"],\r\n    \"min_value\": 50000,\r\n    \"categories\": [\"electronics\", \"furniture\"],\r\n    \"assigned_reps\": [\"user_42\", \"user_67\"]\r\n  },\r\n  \"sort\": { \"field\": \"created_at\", \"direction\": \"desc\" },\r\n  \"pagination\": { \"page\": 1, \"per_page\": 20 }\r\n}<\/code><\/pre>\n<p>The response is identical to what you&#8217;d return from a GET \u2014 just data, no side effects:<\/p>\n<pre><code>HTTP\/1.1 200 OK\r\nContent-Type: application\/json\r\nCache-Control: private, max-age=60\r\n\r\n{\r\n  \"data\": [...],\r\n  \"meta\": {\r\n    \"total\": 147,\r\n    \"page\": 1,\r\n    \"per_page\": 20\r\n  }\r\n}<\/code><\/pre>\n<h2>How It Differs from GET and POST<\/h2>\n<table>\n<thead>\n<tr>\n<th>Property<\/th>\n<th>GET<\/th>\n<th>POST<\/th>\n<th>QUERY<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Safe (no side effects)<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Idempotent<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Request body<\/td>\n<td>\u274c (technically allowed but discouraged)<\/td>\n<td>\u2705<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Cacheable<\/td>\n<td>\u2705<\/td>\n<td>\u274c (by default)<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Semantic meaning<\/td>\n<td>Retrieve resource<\/td>\n<td>Create\/action<\/td>\n<td>Query\/search<\/td>\n<\/tr>\n<tr>\n<td>Bookmarkable<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u274c (body not in URL)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Implementing HTTP QUERY in Laravel<\/h2>\n<p>Laravel&#8217;s router doesn&#8217;t natively support QUERY yet, but you can handle it:<\/p>\n<pre><code>\/\/ routes\/api.php \u2014 register QUERY method route\r\nuse Illuminate\\Support\\Facades\\Route;\r\n\r\nRoute::match(['QUERY'], '\/orders', [OrderController::class, 'query']);\r\nRoute::match(['QUERY'], '\/products', [ProductController::class, 'query']);\r\nRoute::match(['QUERY'], '\/clients', [ClientController::class, 'query']);<\/code><\/pre>\n<pre><code>&lt;?php\r\n\r\nnamespace App\\Http\\Controllers\\Api;\r\n\r\nuse App\\Http\\Controllers\\Controller;\r\nuse App\\Http\\Resources\\OrderResource;\r\nuse App\\Models\\Order;\r\nuse Illuminate\\Http\\Request;\r\nuse Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection;\r\n\r\nclass OrderController extends Controller\r\n{\r\n    \/**\r\n     * Handle HTTP QUERY requests for searching orders.\r\n     *\/\r\n    public function query(Request $request): AnonymousResourceCollection\r\n    {\r\n        $validated = $request->validate([\r\n            'filters'                    => 'array',\r\n            'filters.date_range'         => 'array',\r\n            'filters.date_range.from'    => 'date',\r\n            'filters.date_range.to'      => 'date',\r\n            'filters.status'             => 'array',\r\n            'filters.status.*'           => 'in:pending,processing,completed,cancelled',\r\n            'filters.client_states'      => 'array',\r\n            'filters.min_value'          => 'numeric|min:0',\r\n            'filters.max_value'          => 'numeric',\r\n            'filters.categories'         => 'array',\r\n            'filters.assigned_reps'      => 'array',\r\n            'sort'                       => 'array',\r\n            'sort.field'                 => 'string|in:created_at,total_value,client_name',\r\n            'sort.direction'             => 'in:asc,desc',\r\n            'pagination'                 => 'array',\r\n            'pagination.page'            => 'integer|min:1',\r\n            'pagination.per_page'        => 'integer|min:1|max:100',\r\n        ]);\r\n\r\n        $filters    = $validated['filters'] ?? [];\r\n        $sort       = $validated['sort'] ?? ['field' => 'created_at', 'direction' => 'desc'];\r\n        $pagination = $validated['pagination'] ?? ['page' => 1, 'per_page' => 20];\r\n\r\n        $query = Order::query()\r\n            ->with(['client', 'items', 'assignedRep'])\r\n            ->when(isset($filters['date_range']), function ($q) use ($filters) {\r\n                $q->whereBetween('created_at', [\r\n                    $filters['date_range']['from'],\r\n                    $filters['date_range']['to'],\r\n                ]);\r\n            })\r\n            ->when(!empty($filters['status']), function ($q) use ($filters) {\r\n                $q->whereIn('status', $filters['status']);\r\n            })\r\n            ->when(!empty($filters['client_states']), function ($q) use ($filters) {\r\n                $q->whereHas('client', function ($q) use ($filters) {\r\n                    $q->whereIn('state', $filters['client_states']);\r\n                });\r\n            })\r\n            ->when(isset($filters['min_value']), function ($q) use ($filters) {\r\n                $q->where('total_value', '>=', $filters['min_value']);\r\n            })\r\n            ->when(isset($filters['max_value']), function ($q) use ($filters) {\r\n                $q->where('total_value', '<=', $filters['max_value']);\r\n            })\r\n            ->when(!empty($filters['categories']), function ($q) use ($filters) {\r\n                $q->whereHas('items.product', function ($q) use ($filters) {\r\n                    $q->whereIn('category', $filters['categories']);\r\n                });\r\n            })\r\n            ->when(!empty($filters['assigned_reps']), function ($q) use ($filters) {\r\n                $q->whereIn('assigned_rep_id', $filters['assigned_reps']);\r\n            })\r\n            ->orderBy($sort['field'], $sort['direction']);\r\n\r\n        $results = $query->paginate(\r\n            $pagination['per_page'],\r\n            ['*'],\r\n            'page',\r\n            $pagination['page']\r\n        );\r\n\r\n        return OrderResource::collection($results);\r\n    }\r\n}<\/code><\/pre>\n<h2>Client-Side Implementation<\/h2>\n<pre><code>\/\/ JavaScript \u2014 sending HTTP QUERY requests\r\nasync function searchOrders(filters, sort, pagination) {\r\n  const response = await fetch('\/api\/orders', {\r\n    method: 'QUERY',\r\n    headers: {\r\n      'Content-Type': 'application\/json',\r\n      'Accept': 'application\/json',\r\n      'Authorization': `Bearer ${token}`,\r\n    },\r\n    body: JSON.stringify({ filters, sort, pagination }),\r\n  });\r\n\r\n  if (!response.ok) throw new Error('Search failed');\r\n  return response.json();\r\n}\r\n\r\n\/\/ Usage\r\nconst results = await searchOrders(\r\n  {\r\n    date_range: { from: '2026-07-01', to: '2026-07-25' },\r\n    status: ['pending', 'processing'],\r\n    min_value: 50000,\r\n  },\r\n  { field: 'created_at', direction: 'desc' },\r\n  { page: 1, per_page: 20 }\r\n);<\/code><\/pre>\n<h2>Fallback for Older Clients<\/h2>\n<p>Some HTTP clients and proxies don&#8217;t support custom methods yet. A safe fallback pattern:<\/p>\n<pre><code>\/\/ Support both QUERY and POST with method override header\r\n\/\/ routes\/api.php\r\nRoute::match(['QUERY', 'POST'], '\/orders\/search', [OrderController::class, 'query'])\r\n    ->middleware('api');\r\n\r\n\/\/ Client \u2014 use X-HTTP-Method-Override for compatibility\r\nasync function searchOrders(filters) {\r\n  \/\/ Try QUERY first\r\n  try {\r\n    return await fetch('\/api\/orders', {\r\n      method: 'QUERY',\r\n      headers: { 'Content-Type': 'application\/json' },\r\n      body: JSON.stringify(filters),\r\n    });\r\n  } catch {\r\n    \/\/ Fallback to POST with method override\r\n    return await fetch('\/api\/orders\/search', {\r\n      method: 'POST',\r\n      headers: {\r\n        'Content-Type': 'application\/json',\r\n        'X-HTTP-Method-Override': 'QUERY',\r\n      },\r\n      body: JSON.stringify(filters),\r\n    });\r\n  }\r\n}<\/code><\/pre>\n<h2>Caching HTTP QUERY Responses<\/h2>\n<p>Since QUERY is safe and idempotent, responses can be cached. The cache key includes the request body:<\/p>\n<pre><code>&lt;?php\r\n\r\n\/\/ Laravel middleware \u2014 cache QUERY responses\r\nclass CacheQueryResponses\r\n{\r\n    public function handle(Request $request, Closure $next)\r\n    {\r\n        if ($request->method() !== 'QUERY') {\r\n            return $next($request);\r\n        }\r\n\r\n        \/\/ Cache key = route + body hash\r\n        $cacheKey = 'query:' . $request->path() . ':' . md5($request->getContent());\r\n\r\n        if (Cache::has($cacheKey)) {\r\n            return response()->json(Cache::get($cacheKey))\r\n                ->header('X-Cache', 'HIT');\r\n        }\r\n\r\n        $response = $next($request);\r\n\r\n        Cache::put($cacheKey, $response->getData(), 60); \/\/ 60 seconds\r\n\r\n        return $response->header('X-Cache', 'MISS');\r\n    }\r\n}<\/code><\/pre>\n<h2>Browser and Tool Support in 2026<\/h2>\n<table>\n<thead>\n<tr>\n<th>Tool\/Environment<\/th>\n<th>QUERY Support<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>fetch() API (browsers)<\/td>\n<td>\u2705 Works \u2014 fetch allows any method string<\/td>\n<\/tr>\n<tr>\n<td>Axios<\/td>\n<td>\u2705 Works \u2014 pass method: &#8216;QUERY&#8217;<\/td>\n<\/tr>\n<tr>\n<td>Postman<\/td>\n<td>\u2705 Supported in custom method field<\/td>\n<\/tr>\n<tr>\n<td>curl<\/td>\n<td>\u2705 curl -X QUERY &#8230;<\/td>\n<\/tr>\n<tr>\n<td>Laravel HTTP client<\/td>\n<td>\u2705 Http::send(&#8216;QUERY&#8217;, $url, [&#8230;])<\/td>\n<\/tr>\n<tr>\n<td>Some enterprise proxies<\/td>\n<td>\u26a0\ufe0f May block unknown methods \u2014 test your stack<\/td>\n<\/tr>\n<tr>\n<td>AWS API Gateway<\/td>\n<td>\u26a0\ufe0f Requires explicit QUERY method configuration<\/td>\n<\/tr>\n<tr>\n<td>Cloudflare<\/td>\n<td>\u2705 Passes through unknown methods by default<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>When to Use QUERY vs GET vs POST<\/h2>\n<p><strong>Use GET when:<\/strong> The search fits comfortably in URL parameters. Simple filters \u2014 status, date, category. URLs should be bookmarkable and shareable.<\/p>\n<p><strong>Use QUERY when:<\/strong> Complex nested filters that don&#8217;t fit in a URL. Structured search payloads with multiple filter types. You want proper REST semantics for read-only operations. You need response caching for expensive search operations.<\/p>\n<p><strong>Use POST when:<\/strong> The operation has side effects (creating, triggering, updating). You&#8217;re working with systems that can&#8217;t support custom HTTP methods. The &#8220;search&#8221; also triggers logging, analytics, or other state changes.<\/p>\n<p>RFC 10008 is a recent standard and real-world adoption will take time. But the pattern it formalizes \u2014 safe body-carrying requests \u2014 is one that good API designers have been working around for years. Now there&#8217;s a proper name and spec for it.<\/p>\n<p>If you&#8217;re designing a new API and want to get the HTTP semantics right from the start, <a href=\"https:\/\/softcrony.com\/contact\/\">our backend team at Softcrony is happy to help with API architecture<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>HTTP has had the same core methods since the 1990s \u2014 GET, POST, PUT, PATCH, DELETE. They&#8217;ve served the web well. But one scenario has always been awkward: complex searches that need a request body. RFC 10008, published in 2026, formalizes the HTTP QUERY method \u2014 a new HTTP verb specifically designed for safe, idempotent [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":186,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[62,34,148,19,149,150],"class_list":["post-184","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-api","tag-backend","tag-http","tag-laravel","tag-rest","tag-rfc-10008"],"_links":{"self":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/comments?post=184"}],"version-history":[{"count":0,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/184\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media\/186"}],"wp:attachment":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media?parent=184"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/categories?post=184"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/tags?post=184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}