Skip to main content

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​

MethodPlain-English meaningTypical use
GETRead somethingGet a user or a short filtered list
POSTCreate something or ask the server to process an actionCreate an order or submit a form
PUTReplace a resource with the supplied versionReplace all of a profile
PATCHChange part of a resourceChange only a profile's display name
QUERYRead using a structured, possibly complex querySearch 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 GET for a resource lookup and simple URL-sized filters.
  • Use QUERY for a read-only search with structured filters, arrays, nested criteria, pagination, or other data that would make the URL unwieldy.
  • Use POST when the request creates data or triggers a state-changing action.
  • Use PUT to replace a resource and PATCH to 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.