skip to content

A search endpoint needs a complex filter object that no longer fits comfortably in a query string. Would you keep it as an HTTP GET or switch to POST, and what do you lose either way?

level: middleimportance: must knowfreq 52%

answer

  1. GET: cacheable, linkable, honestly safe
  2. ~2 KB practical URL budget; 414 beyond
  3. Query strings land in logs, history, Referer
  4. POST /search: any body, no shared caching, no auto-retry
  5. QUERY method = GET with a body (proposed)

basics

~20 s

GET keeps the search cacheable, linkable and obviously read-only, but URL length limits and logging of query strings constrain it. POST allows an arbitrary JSON filter body but is not cacheable or safe by default, so it hides a read behind a write method. The proposed QUERY method exists to close that gap.

solid answer

~50 s

**Stay on GET** while the filter fits: roughly 2,000 characters is the practical safe ceiling across browsers, proxies and servers. You keep cacheability (shared caches, CDN, conditional requests), shareable URLs, automatic retries, and an honest signal that the call is read-only. **Switch to POST** — conventionally `POST /orders/search` or `POST /orders/query` — when the filter is deeply structured, exceeds URL limits, or contains data that must not sit in logs, browser history and `Referer` headers. The costs: HTTP caches will not reuse the response, intermediaries will not retry it, and generic tooling can no longer tell this POST is a read. Mitigations: document that the endpoint is read-only, keep it side-effect free, and if you need caching, add your own layer or expose a `GET` variant for the common cases. The IETF's proposed **QUERY** method is precisely "a GET with a body": safe, idempotent and cacheable, with the filter in the payload — worth naming, but not yet something you can rely on in production.

go deeper

for a junior

Say GET for simple filters because it is a read and can be cached and shared, POST when the filter is too big or complex for a URL.

for a middle

Quantify the URL budget, name the caching and retry losses on POST, and mention query strings leaking into logs.

for a senior

Weigh application-level caching, saved-search resources, supporting both shapes, and keeping the read honest for monitoring and policy.

for a principal

Set the org convention for search endpoints and their caching strategy, and track the QUERY proposal as the eventual clean answer.

## The genuine tension Search is a read, and reads want GET. But GET's parameters live in the URL, and URLs are a poor place for a nested filter object: ``` GET /orders?status=paid&status=shipped&createdAfter=2026-01-01& amount.gte=100&amount.lte=500&customer.country=DE&sort=-createdAt ``` That is already ugly, and it degrades fast once you need OR-groups, nested boolean logic, or a list of 5,000 ids. ## Why GET is still the default **Cacheability.** GET responses are cacheable by URL. That is not just browser caching — it is CDNs, reverse proxies, and conditional requests with `ETag`/`If-None-Match` returning `304 Not Modified`. For a popular search this can remove most of the load. POST responses are, in practice, not cached by shared caches. **Linkability.** The URL *is* the query. Users bookmark it, paste it into tickets, and share it. Reproducing a bug becomes "open this link". **Honest semantics.** Every intermediary knows a GET is safe: it can retry it, prefetch it, and log it as a read. Monitoring separates reads from writes for free. ## Why GET eventually fails **Length limits.** The spec sets no maximum, but reality does: many servers and proxies cap the request line around 8 KB, and older browsers around 2 KB. Exceed it and you get `414 URI Too Long` — or worse, a silent truncation in some middlebox. Treat ~2,000 characters as the safe budget for anything public. **Structure.** Query strings are a flat list of key-value pairs. Encoding nested boolean logic means inventing a mini-language (`filter[and][0][field]=...`, or a base64-encoded JSON blob). Both are unpleasant to write, unpleasant to validate, and unpleasant to document. **Sensitive data.** Query strings appear in server access logs, proxy logs, browser history and the `Referer` header sent to third-party assets. Searching by national id, email, or medical term through a query string spreads that term through your logging estate and possibly to external domains. This alone is often the decisive argument for POST. ## What POST-for-search costs ``` POST /orders/search Content-Type: application/json {"status":["paid","shipped"],"amount":{"gte":100,"lte":500}, "createdAfter":"2026-01-01","sort":["-createdAt"],"limit":50} ``` - **No HTTP caching.** You must build your own (application-level cache keyed by a hash of the body) if it matters. - **No automatic retries.** Proxies and clients treat POST as unsafe, which is correct in general and inconvenient here. - **Semantic muddiness.** A generic client, a log dashboard, or a security policy sees a write. Teams mitigate this by naming the endpoint clearly (`/search`, `/query`), documenting it as read-only, and never mutating in it — but the wire-level signal is gone. - **Verb-in-URL.** `/orders/search` is a small departure from resource purity. It is universally accepted in practice; the alternative — POSTing a `search` resource that returns `201` with a results URL — is defensible and occasionally useful for very expensive, cacheable, shareable queries, but it costs a round trip. ## Practical patterns **Support both.** `GET /orders?...` for simple, cacheable, linkable filters; `POST /orders/search` for the heavy cases. Slightly more surface, but each client uses the one that fits. **Saved-search resource.** `POST /searches` creates a filter resource returning `201 Location: /searches/abc`, then `GET /searches/abc/results` is cacheable and shareable. Good for expensive analytical queries and for very long filters; overkill for ordinary listing. **Never tunnel a mutation.** Whatever shape you choose, a search endpoint must remain side-effect free apart from logging. The moment it also mutates, you have lost the ability to reason about it at all. ## The QUERY method The IETF has a proposal for a **QUERY** method: semantically a GET that carries a request body, defined as safe, idempotent and cacheable (with the cache key incorporating the body). It resolves the tension directly. Mentioning it shows you know the design space; relying on it today does not work, since client libraries, proxies and frameworks largely do not support it. `X-HTTP-Method-Override`-style tunnelling is not a substitute — it is still a POST to everything in the path. ## How to answer in an interview Start with "GET while it fits, POST when it does not", then name the three forces — URL length, structure, sensitive data in logs — and the three costs — caching, retries, semantic clarity. Finish with the QUERY proposal and the both-endpoints pragmatic compromise.

  • If you move search to POST, how do you get caching back?
    Shared HTTP caches will not help, so you add your own: an application or edge cache keyed by a normalized hash of the request body plus the caller's authorization scope, with a short TTL. You can also expose a GET variant for the common, small filters so the hottest queries stay HTTP-cacheable, and use ETags on that path for conditional requests.
  • What is the QUERY method and why was it proposed?
    QUERY is a proposed HTTP method with GET-like semantics — safe, idempotent, and cacheable — that carries a request body, so complex filters do not have to be squeezed into a URL or hidden behind POST. Its cache key includes the body. It directly solves the search dilemma, but tooling support is still thin, so today it is a design-discussion answer rather than a production choice.

saying these in an interview costs you the question

  • Claiming a request body on GET is impossible — it is permitted but widely ignored and unreliable, which is the real reason not to use it
  • Putting personal data or secrets in a search query string without considering logs and Referer
  • Believing POST search responses will be cached by CDNs or browsers
  • Making a search endpoint mutate state because it is already a POST
  • Assuming a fixed URL length limit exists in the specification

context