HTTP QUERY: complex reads without misusing POST
Most APIs start with a small set of HTTP methods: GET, POST, PUT, and PATCH.
They cover the majority of work. But there is a useful gap: how should an API express a
complex search that only reads data?
The HTTP QUERY method is designed for that gap. It is a standardized HTTP method
(RFC 10008) for sending a structured query to a resource without changing that resource.
Start with the familiar methods
| Method | Plain-English meaning | Typical use |
|---|---|---|
GET | Read something | Get a user or a short filtered list |
POST | Create something or ask the server to process an action | Create an order or submit a form |
PUT | Replace a resource with the supplied version | Replace all of a profile |
PATCH | Change part of a resource | Change only a profile's display name |
QUERY | Read using a structured, possibly complex query | Search with many filters, arrays, and pagination |
GET and QUERY are both safe: the client is asking to read, not to change server state.
They are also idempotent: sending the same request again does not create or update anything.
The returned data can still differ later if the data itself has changed.
When GET is the right choice
Use GET for a direct read or a small number of simple filters:
GET /api/work-logs?from=2026-09-01&to=2026-09-30&type=Work
This is easy to read, link to, bookmark, and cache. There is no reason to introduce
QUERY merely because an endpoint returns a list.
The problem with a complex query string
Imagine a screen where a user filters work logs by several categories, types, work modes,
projects, JIRAs, a date range, and pagination. A GET URL can become hard to create,
read, validate, and maintain:
GET /api/work-logs?category=Office%20Work&category=Grooming&type=Work&type=Support&workMode=WFH&projectId=...&jiraId=...&pageSize=25
Arrays and nested conditions are especially awkward in a URL. A GET request body is not
the solution: HTTP does not define useful, portable semantics for a body on GET.
Historically, many APIs used POST /search with a JSON body. That works, but POST does
not tell a caller that the operation is read-only. QUERY keeps the JSON body while making
the read-only intent explicit.
A QUERY request
The Work Tracker API uses QUERY for endpoints that need richer server-side filters:
QUERY /api/work-logs
Authorization: Bearer <access-token>
Content-Type: application/json
{
"filters": {
"categories": ["Office Work", "Grooming"],
"types": ["Work", "Support"],
"workModes": ["WFO (Office)", "WFH (Home)"],
"projectIds": ["project-page-id"],
"appraisal": false
},
"pageSize": 25,
"includeRelations": true
}
The body is regular JSON, so the shape is much clearer than a long URL. The API validates
the allowed filters, applies them on the server, and returns matching records. Clients send
domain filters such as categories and projectIds; they do not need to know the database
provider's query format.
Calling QUERY from JavaScript
fetch accepts an HTTP method as a string, so no special client library is required:
const response = await fetch("https://api.example.com/api/work-logs", {
method: "QUERY",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
filters: {
categories: ["Office Work", "Grooming"],
types: ["Work", "Support"],
},
pageSize: 25,
}),
});
if (!response.ok) {
throw new Error(`Query failed: ${response.status}`);
}
const result = await response.json();
For a browser call to another origin, the API must allow QUERY in its CORS preflight
response, for example:
Access-Control-Allow-Methods: GET, QUERY, POST, PATCH, DELETE, OPTIONS
An endpoint that supports JSON query content can advertise that fact with:
Accept-Query: application/json
GET vs QUERY: a simple rule
Choose the smallest, clearest request that describes the job:
- Use
GETfor a resource lookup and simple URL-sized filters. - Use
QUERYfor a read-only search with structured filters, arrays, nested criteria, pagination, or other data that would make the URL unwieldy. - Use
POSTwhen the request creates data or triggers a state-changing action. - Use
PUTto replace a resource andPATCHto change part of one.
QUERY is additive, not a replacement for the other REST methods. For example, the Work
Tracker API continues to use GET for simple retrieval, POST for creation, and PATCH
for partial updates; it uses QUERY for complex read endpoints such as work logs, JIRAs,
tasks, and feedback.
Important implementation notes
QUERY is newer than the methods that have been used on the web for decades. Before making
it part of a public API, verify that your clients, API gateway, reverse proxy, security
rules, observability tools, and CORS configuration accept and preserve the method.
Keep the method honest: a QUERY handler must not create records, update records, send an
email, or trigger another side effect. If it changes state, it should be a different method—
usually POST, PUT, or PATCH.
When the request is just a small, shareable URL, prefer GET. QUERY earns its place when
the query itself is complex enough that a structured body makes the API easier and safer to
use.