HTTP QUERY Method
The HTTP QUERY method (RFC 10008) is like GET — safe, idempotent, and cacheable — but carries a query in the request body with a Content-Type. It's the standard answer to "I need a GET, but my query is too large or too structured for the URL".
H3 supports QUERY as a first-class method via app.query(), plus two helper utilities.
Register a QUERY Handler
Read the request body just like you would for a POST:
import { readBody } from "h3";
app.query("/books", async (event) => {
const query = await readBody(event, { type: "text" });
return runSearch(query);
});
Because QUERY carries an attacker-controllable body, body-size limits apply just like POST.
Advertise Accepted Formats
Use appendAcceptQuery to tell clients which query formats a resource understands. It sets the Accept-Query response header (a Structured Fields List), and can be set on a plain GET too so clients can discover formats before sending a QUERY:
import { appendAcceptQuery } from "h3";
app.get("/books", (event) => {
appendAcceptQuery(event, ["application/sql", "application/jsonpath"]);
// Accept-Query: application/sql, application/jsonpath
return "Send a QUERY request with a SQL or JSONPath body.";
});
Validate the Content-Type
Use requireContentType to enforce the RFC's error semantics. It returns the matched media type, or throws 400 (missing), 415 (unsupported), or 422 (malformed):
import { requireContentType, readBody } from "h3";
app.query("/books", async (event) => {
const type = requireContentType(event, ["application/sql", "application/jsonpath"]);
const query = await readBody(event, { type: "text" });
return runQuery(type, query);
});
Offer a Cacheable GET Alternative
A QUERY response is not addressable by URL, so browsers and CDNs can't cache it. RFC 10008 suggests pointing clients at an equivalent, cacheable GET via the Content-Location header. Stash the result under a stable id and let a client repeat the query with an ordinary, HTTP-cacheable GET:
app.query("/books", async (event) => {
const result = runQuery(type, query);
const id = queryId(type, query); // stable hash of the query
cache.set(id, result);
event.res.headers.set("content-location", `/books/${id}`);
return result;
});
Full Example
A self-contained, runnable demo — a /books resource that accepts SQL-ish and JSONPath queries, validates the Content-Type, and advertises a cacheable GET alternative. It also serves a small interactive page at /.
GET, QUERY is not CORS-safelisted, so browsers send a preflight. If you pass an explicit methods allowlist to handleCors, include "QUERY".