Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /Blog
  3. /HTTP QUERY Method in Express and Node.js
backend9 min read

HTTP QUERY Method in Express and Node.js

How to actually implement the new HTTP QUERY method (RFC 10008) in Express and Node.js, with working code for a search endpoint, CORS, and a POST fallback.

Zeeshan Tofiq
Zeeshan Tofiq
July 7, 2026
On this page

On this page

  • Current Framework Support Status
  • Adding QUERY Support with the Raw Node.js http Module
  • Adding QUERY Support to Express (The Practical Workaround)
  • A Complete Working Example: Search Endpoint
  • Handling CORS Preflight for QUERY
  • Testing QUERY Requests
  • Fallback Pattern for Clients
  • Common Pitfalls When Adopting QUERY
  • Frequently Asked Questions

RFC 10008 standardized the HTTP QUERY method in June 2026, the first new HTTP method since PATCH landed in 2010. QUERY is safe and idempotent like GET, but it carries a request body like POST. That combination finally gives search and filter endpoints a proper home instead of the POST /search workaround nearly every API has leaned on for years.

Plenty has already been written about what QUERY is and why it exists. This post skips that part and goes straight to what actually matters if you are building APIs today: how do you add QUERY support to a real Node.js server right now, given that framework support is still catching up.

If you just want to know whether your specific stack, proxy, or client already understands QUERY before you commit engineering time to it, check the HTTP QUERY Support Matrix for a live, filterable compatibility reference instead of hunting through GitHub issues yourself.

Current Framework Support Status

This table reflects support status as of this article's publish date. Framework support for a brand-new HTTP method evolves quickly, so verify current status before relying on this for a production decision. The HTTP QUERY Support Matrix tracks this across frameworks, proxies, and clients on an ongoing basis, with a source link for every row.

LayerStatusNotes
Node.js core (node:http)SupportedLanded in Node 21.7.2, tracked in nodejs/node#51562
ExpressManual workaround requiredRouter does not yet expose app.query(), tracked in expressjs/express#5615, open as of publish
FastifyCheck current release notesVerify against the Fastify changelog before depending on native support
HonoCheck current release notesVerify against Hono's routing documentation
Browsers (fetch)InconsistentCheck MDN's current compatibility table before relying on client-side fetch(method: 'QUERY')

The practical takeaway: Node.js itself already understands QUERY as a method. Getting your web framework's router to dispatch QUERY requests to the right handler is the part that needs manual work right now.

Adding QUERY Support with the Raw Node.js http Module

Since Node core already parses QUERY as a valid method, a raw node:http server handles it without any extra configuration. This is worth seeing first because it proves the method itself works end to end before you deal with framework-level routing.

javascript — server.js
const http = require('node:http');

const server = http.createServer((req, res) => {
  if (req.method === 'QUERY') {
    let body = '';
    req.on('data', (chunk) => { body += chunk; });
    req.on('end', () => {
      const filters = JSON.parse(body);
      console.log('QUERY request received with filters:', filters);

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ results: [], filters }));
    });
    return;
  }

  res.writeHead(404);
  res.end();
});

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});

Test it directly with curl to confirm your Node.js version handles it correctly:

bash
curl -X QUERY http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{"category":"electronics","priceMax":500}'

If you get back a JSON response instead of a connection error or a 405, both your Node.js version and this basic handling pattern work as expected. Everything else in this article builds on top of that baseline.

Adding QUERY Support to Express (The Practical Workaround)

Express's router does not currently expose a first-class app.query() method the way it exposes app.get() or app.post(). Until that lands, the practical approach is a small piece of middleware that intercepts QUERY requests before they hit Express's method-based routing and dispatches them manually.

javascript — app.js
const express = require('express');
const app = express();

app.use(express.json());

// Manual QUERY method routing middleware
// Place this before your other route definitions
const queryRoutes = new Map();

function query(path, handler) {
  queryRoutes.set(path, handler);
}

app.use((req, res, next) => {
  if (req.method === 'QUERY') {
    const handler = queryRoutes.get(req.path);
    if (handler) {
      return handler(req, res, next);
    }
    return res.status(404).json({ error: 'No QUERY handler for this path' });
  }
  next();
});

// Register a QUERY route the same way you'd register app.get()
query('/products', (req, res) => {
  const filters = req.body;
  console.log('Searching products with filters:', filters);
  res.json({ results: [], appliedFilters: filters });
});

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

This pattern is intentionally minimal so it is easy to adapt. If you need path parameters, like /products/:category, bring in a small path-matching library, such as path-to-regexp, which Express itself uses internally, rather than the plain Map lookup shown here. For a broader look at how modern routers structure middleware and validation around a request, this walkthrough of building REST APIs with Hono's routing patterns covers a framework that takes a more explicit, method-driven approach than Express.

ℹ Body parsing works exactly like POST

express.json() parses the body based on Content-Type, not HTTP method, so it works for QUERY requests exactly the same way it works for POST. You don't need any special body-parsing configuration for QUERY specifically.

A Complete Working Example: Search Endpoint

Here is the exact use case RFC 10008 was designed for: a product search endpoint with a structured filter body, replacing the common POST /products/search anti-pattern.

javascript — search-example.js
const express = require('express');
const app = express();
app.use(express.json());

const products = [
  { id: 1, name: 'Laptop', category: 'electronics', price: 899 },
  { id: 2, name: 'Desk Chair', category: 'furniture', price: 249 },
  { id: 3, name: 'Headphones', category: 'electronics', price: 179 },
  { id: 4, name: 'Monitor', category: 'electronics', price: 329 },
];

const queryRoutes = new Map();
function query(path, handler) {
  queryRoutes.set(path, handler);
}

app.use((req, res, next) => {
  if (req.method === 'QUERY') {
    const handler = queryRoutes.get(req.path);
    if (handler) return handler(req, res, next);
    return res.status(404).json({ error: 'Not found' });
  }
  next();
});

query('/products', (req, res) => {
  const { category, priceMax, priceMin, sort } = req.body;

  let results = products;

  if (category) {
    results = results.filter((p) => p.category === category);
  }
  if (priceMax !== undefined) {
    results = results.filter((p) => p.price <= priceMax);
  }
  if (priceMin !== undefined) {
    results = results.filter((p) => p.price >= priceMin);
  }
  if (sort === 'price_asc') {
    results = [...results].sort((a, b) => a.price - b.price);
  } else if (sort === 'price_desc') {
    results = [...results].sort((a, b) => b.price - a.price);
  }

  res.json({ count: results.length, results });
});

app.listen(3000, () => console.log('Server on port 3000'));

Test the full filtering behavior:

bash
curl -X QUERY http://localhost:3000/products \
  -H "Content-Type: application/json" \
  -d '{"category":"electronics","priceMax":400,"sort":"price_asc"}'

Compare this to what the same request looked like as a POST-based workaround before QUERY existed. Functionally identical, but now the method itself communicates "this is safe, idempotent, and cacheable" to any intermediary that understands it, without any documentation needed. That distinction matters more than it looks: a GET or QUERY request can be cached, retried automatically, and prefetched by a client without side effects, while a POST request cannot be assumed safe to retry blindly. If caching semantics on read-heavy endpoints are new territory for your team, this breakdown of caching strategies covers the tradeoffs QUERY's cacheable-but-body-keyed nature runs into in practice.

Handling CORS Preflight for QUERY

QUERY is not part of the CORS-safelisted method set, which only covers GET, HEAD, and POST with specific content types. This means any cross-origin QUERY request triggers a browser preflight OPTIONS request first. If your API is called from a different origin than where it is hosted, you need to handle this explicitly.

javascript — cors-setup.js
const cors = require('cors');

app.use(cors({
  origin: 'https://your-frontend-domain.com',
  methods: ['GET', 'POST', 'QUERY', 'OPTIONS'], // QUERY must be explicit here
  allowedHeaders: ['Content-Type'],
}));

⚠ Without QUERY listed explicitly, the browser call fails silently

Without QUERY explicitly listed in the methods array, the cors middleware package rejects the preflight check and your QUERY requests fail from the browser, even though they work fine from curl or a server-to-server call. This is one of the most common "it works in Postman but not in the browser" issues you will hit when adding QUERY to a public-facing API. If you are handling CORS manually without the cors package, make sure your Access-Control-Allow-Methods response header on the OPTIONS preflight response includes QUERY explicitly.

Testing QUERY Requests

With curl, the -X flag sets the method directly, and QUERY works like any other method:

bash
curl -X QUERY http://localhost:3000/products \
  -H "Content-Type: application/json" \
  -d '{"category":"electronics"}'

With fetch() in the browser or Node.js, check current support before relying on it in production. As of this writing, support is inconsistent across environments:

javascript
// This may or may not work depending on your runtime's fetch implementation
// Verify against current browser/Node.js compatibility before shipping
const response = await fetch('http://localhost:3000/products', {
  method: 'QUERY',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ category: 'electronics' }),
});

If fetch() with method: 'QUERY' throws or silently fails in your target environment, you are not doing anything wrong. Client-side support is genuinely still catching up to the server-side story. This is exactly why the fallback pattern in the next section matters.

With Postman or Insomnia, most modern versions accept custom or recently added HTTP methods in the method dropdown, or let you type a custom method name directly. Check your specific client version if the dropdown does not list QUERY yet. Once you have an endpoint working locally, wiring a QUERY-based request into your existing CI checks is no different from any other route: if you already run integration tests against pull requests, this CI/CD walkthrough covers testing new API endpoints as part of your pipeline before they reach production.

Fallback Pattern for Clients

Because QUERY support varies across proxies, older HTTP clients, and some browser environments, a defensive client-side pattern attempts QUERY first and falls back to the POST workaround if the server or an intermediary does not support it.

javascript — search-client.js
async function searchProducts(filters) {
  try {
    const response = await fetch('/products', {
      method: 'QUERY',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(filters),
    });

    if (response.status === 405 || response.status === 501) {
      // Method not supported by this server or an intermediary
      throw new Error('QUERY not supported, falling back');
    }

    return response.json();
  } catch (err) {
    // Fall back to the POST-based search endpoint
    const response = await fetch('/products/search', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(filters),
    });
    return response.json();
  }
}

On the server side, this means maintaining both a QUERY handler and a POST fallback handler pointing at the same underlying search logic during the transition period, until QUERY support is reliably present across your entire request path: your client, any proxies or CDNs in between, and your server framework.

javascript — shared-handler.js
// Shared handler logic, called from both QUERY and POST fallback routes
function handleProductSearch(req, res) {
  const { category, priceMax, priceMin, sort } = req.body;
  // ... same filtering logic as before
  res.json({ count: results.length, results });
}

query('/products', handleProductSearch);
app.post('/products/search', handleProductSearch); // fallback route

This dual-route approach is the pragmatic path most teams are taking right now: add QUERY support where you can, keep the POST fallback available, and retire the fallback once support has genuinely stabilized across your stack. Since that stabilization is a moving target, it is worth periodically re-checking the current compatibility status across your frameworks, proxies, and clients rather than assuming today's manual workaround is still necessary six months from now.

Common Pitfalls When Adopting QUERY

A handful of mistakes show up repeatedly in early QUERY implementations, mostly because teams treat it as a drop-in replacement for POST rather than a method with its own specific contract. Being aware of these before you ship saves a round of confused bug reports later.

  • Assuming every intermediary passes it through unmodified: older reverse proxies, some corporate firewalls, and certain API gateways only allow a fixed allowlist of HTTP methods. A QUERY request can be silently dropped or rewritten to GET before it ever reaches your server, which looks identical to a routing bug on your own code. Test through your actual production network path, not just localhost.
  • Skipping body size limits: because QUERY looks like GET, it is easy to forget that it carries a body the same way POST does. Apply the same request body size limits you already enforce on POST endpoints (express.json({ limit: '100kb' }), for example), since a QUERY endpoint accepts arbitrary JSON from a client exactly like any other body-carrying method.
  • Not validating that the body is actually JSON: a malformed or missing Content-Type header on a QUERY request will produce the same parsing failures you would see on a broken POST request. Validate and return a clear 400 response rather than letting an unhandled JSON.parse exception crash the handler.
  • Overtrusting the safe and idempotent guarantee: QUERY being specified as safe and idempotent describes intended semantics, not an enforced runtime guarantee. Nothing stops a handler registered on a QUERY route from writing to a database. Treat the safety guarantee as a contract you are responsible for upholding in your own handler code, the same way GET has always relied on developer discipline rather than framework enforcement.
  • Caching without a body-aware cache key: a caching layer that keys purely on URL path, the way it might for a simple GET, will return the wrong cached response for two QUERY requests to the same path with different filter bodies. Any cache in front of a QUERY endpoint needs to incorporate the request body into its cache key, not just the path and query string.

Frequently Asked Questions

What is the HTTP QUERY method?

QUERY is an HTTP method standardized by RFC 10008 in June 2026. It combines GET's safe and idempotent semantics with POST's ability to carry a structured request body, giving search and filter operations a method that accurately describes their intent instead of overloading POST or GET.

Does Express support the HTTP QUERY method natively?

Not yet as a first-class router method. Express does not currently expose app.query() the way it exposes app.get() or app.post(), and the tracking issue (expressjs/express#5615) remains open as of this article's publish date. The workaround is a small piece of middleware that intercepts requests where req.method === 'QUERY' and dispatches them to registered handlers manually, shown in the Express section above.

How is HTTP QUERY different from POST for a search endpoint?
PropertyPOST /searchQUERY
Safe (no side effects)Not guaranteed by the method itselfYes, by specification
IdempotentNot guaranteedYes, by specification
Cacheable by intermediariesNoYes, in principle
Carries a request bodyYesYes
Communicates intent in HTTP semanticsNo, relies on the URL path conventionYes, the method itself signals read-only intent

Both can send the same JSON filter body and both work today. The difference is semantic: QUERY tells proxies, caches, and other tooling that the request is safe to retry and potentially cache, without needing a code comment or API doc to explain the convention.

Does Node.js support the HTTP QUERY method out of the box?

Yes. Node.js core added QUERY method parsing in Node 21.7.2, tracked in nodejs/node#51562. A raw node:http server handles req.method === 'QUERY' without any extra configuration, as shown in the raw HTTP example above. The gap is entirely at the framework routing layer, not in Node itself.

Why does my QUERY request fail from the browser but work from curl?

This is almost always a CORS preflight issue. QUERY is not in the CORS-safelisted method set, so any cross-origin request triggers a browser preflight OPTIONS check first. If your CORS configuration's methods array does not explicitly list QUERY, the preflight fails and the browser blocks the request, even though the same request succeeds from curl or a server-to-server call that never goes through CORS. See the CORS section above for the exact configuration fix.

Should I switch my search endpoints to QUERY today?

It depends on your full request path, not just your server. Node.js core is ready, but you also need to account for your framework's router, any reverse proxy or CDN in front of your API, and the HTTP clients that will call it, including browser fetch() support, which is still inconsistent.

  • Safe to start now: server-to-server APIs where you control both ends, since curl-style and most backend HTTP clients already support custom methods correctly
  • Add a fallback first: public-facing browser APIs, since fetch() support varies and a cross-origin CORS misconfiguration will silently break requests
  • Check before committing: your specific CDN or reverse proxy, since some older proxy configurations reject unrecognized methods outright

The dual-route pattern in the fallback section above lets you adopt QUERY today while keeping a POST-based safety net until your entire stack has caught up.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Related Articles

backend

Hono.js Tutorial: REST API with Zod, JWT & Cloudflare Workers

Step-by-step Hono.js tutorial: routing, middleware, Zod validation, JWT auth, and deployment to Cloudflare Workers and Node.js. Working code throughout.

Jun 15, 2026·14 min read
devops

GitHub Actions Tutorial: CI/CD from Push to Deploy (2026)

Learn GitHub Actions: write your first workflow, run tests automatically, use secrets safely, deploy via SSH, cache dependencies, and run matrix builds.

Jun 12, 2026·13 min read
devops

Caching Strategies Explained: CDN, Redis & DB Cache

A practical guide to caching strategies: browser cache, CDN, in-process memory, and Redis. Learn which layer to use, cache-aside patterns, and invalidation.

Jun 13, 2026·13 min read

On this page

  • Current Framework Support Status
  • Adding QUERY Support with the Raw Node.js http Module
  • Adding QUERY Support to Express (The Practical Workaround)
  • A Complete Working Example: Search Endpoint
  • Handling CORS Preflight for QUERY
  • Testing QUERY Requests
  • Fallback Pattern for Clients
  • Common Pitfalls When Adopting QUERY
  • Frequently Asked Questions
Advertisement