npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

http-queryable

v0.1.1

Published

QUERY endpoints + correct body-aware caching for Node. The new HTTP QUERY method (RFC 10008) made easy and safe — Express, Fastify, raw http, and an isomorphic client.

Readme

http-queryable

The new HTTP QUERY method is here (RFC 10008, IETF, June 2026) — but there's no easy, safe way to use it in Node. http-queryable gives you QUERY endpoints and correct body-aware caching in a few lines — for Express, Fastify, raw http, and an isomorphic browser/Node client.

npm CI license node types PRs welcome Contributor Covenant

http-queryable 30-second demo

Same-meaning body (re-spaced) → cache HIT and the handler doesn't re-run; a different body → the correct different result, never a stale hit.

The problem QUERY solves. For decades, a "read with a big input" forced a bad choice:

| Method | Body? | Safe & idempotent? | Cacheable? | | ----------- | ----------------------------------------------- | ------------------------------------------------------ | ---------- | | GET | ❌ no (cram it into the URL, hit length limits) | ✅ yes | ✅ yes | | POST | ✅ yes | ❌ no (can't safely retry; caches treat it as a write) | ❌ no | | QUERY | ✅ yes | ✅ yes | ✅ yes |

QUERY is "GET with a body" — a body like POST, but the semantics of GET: safe (no side effects), idempotent (retryable), cacheable. It's built for search / filter / graph-style reads that don't fit in a URL, without abusing POST.

QUERY /search HTTP/1.1
Content-Type: application/json

{ "q": "cats", "filters": { "color": "black" } }

Why caching it is the hard part. Shared HTTP caches key on method + URL. With QUERY, many different bodies hit the same URL:

QUERY /search  {"q":"cats"}   → cats results
QUERY /search  {"q":"dogs"}   → dogs results   ← same URL!

A method+URL cache would happily serve the cats response to the dogs request. That's a correctness/security bug — RFC 10008 §2.7 requires the request body to be part of the cache key. http-queryable does that correctly and conservatively: it only treats bodies as equal when it can prove they mean the same thing.

The golden rule: a false cache miss is harmless (you recompute); a false cache hit (two different bodies sharing a key) is a bug — so when in doubt, it caches less.

📖 Want the full rationale and the tricky edge cases? Read docs/why-query.md (5-min deep dive).


Quickstart — a QUERY endpoint in ~5 lines

npm install http-queryable express
import express from "express";
import { queryable, QueryCache } from "http-queryable/express";

const app = express();
app.use(queryable({ cache: new QueryCache() })); // QUERY + safe caching
app.query("/search", (req, res) => res.json(search(req.body)));
app.listen(3000);

Hit it with a body — QUERY is "GET with a body":

curl -X QUERY localhost:3000/search \
  -H 'content-type: application/json' \
  -d '{"q":"cats"}'

The differentiator

The only QUERY library that caches correctly — a different request body never gets another body's cached response.

Shared HTTP caches key on method + URL only. With QUERY, many different bodies hit the same URL, so a naive cache can serve the wrong result. RFC 10008 §2.7 requires the request body to be part of the cache key. http-queryable does exactly that — and does it conservatively, so it never produces a false hit.

curl -X QUERY /search -d '{"q":"cats"}'      # → X-Query-Cache: MISS  → cats result
curl -X QUERY /search -d '{ "q" : "cats" }'  # → X-Query-Cache: HIT   (same meaning, re-spaced)
curl -X QUERY /search -d '{"q":"dogs"}'      # → X-Query-Cache: MISS  → dogs result (NOT stale cats)

The second call is a hit because the body means the same thing (key order and whitespace don't matter). The third is a miss with the correct result because the body is genuinely different. Even the existing Fastify QUERY plugin punts on this and tells you to keep responses private — we solve it.

Why QUERY

  • GET-safe + POST-body + cacheable. Safe, idempotent reads that carry a real request body — no more cramming complex queries into the URL.
  • It's a standard. RFC 10008 (June 2026) defines the method, Accept-Query negotiation, and that the cache key includes the body.
  • Kills real pain. No more URL-length limits on big search/filter payloads, and no more abusing POST for reads (which breaks caching and idempotency).

Supported

| Target | Support | | ------------------------- | ------------------------------------------------------- | | Express (4 & 5) | ✓ app.query() + safe caching, CORS preflight, helpers | | Fastify (≥5) | ✓ plugin reusing the same caching core | | raw node:http | ✓ createQueryServer / createQueryListener | | Browser + Node client | ✓ isomorphic query(url, body, opts) |

Requirements

Node >= 22. QUERY must be accepted by the runtime's HTTP parser; Node lists QUERY in http.METHODS starting at v22. http-queryable detects this at startup and fails with an actionable error on older Node.


The client (browser + Node)

import { query } from "http-queryable/client";

const { response } = await query("https://api.example.com/search", { q: "cats" });
const results = await response.json();
  • Sets Content-Type and serializes the body (JSON by default).
  • Optional Accept-Query discovery via OPTIONS ({ discover: true }).
  • Optional follow of Content-Location/Location to GET the canonical result ({ followResult: true }), per RFC 9110 §10.2.2.
  • Safe auto-retry with backoff — sound because QUERY is idempotent (RFC 10008 §2).

Fastify

import Fastify from "fastify";
import { fastifyQueryable, QueryCache } from "http-queryable/fastify";

const app = Fastify();
await app.register(fastifyQueryable, { cache: new QueryCache() });
app.route({
  method: "QUERY",
  url: "/search",
  handler: (req, reply) => reply.send(search(req.body)),
});

Raw http

import { createQueryServer, QueryCache } from "http-queryable/http";

createQueryServer(
  (req, res, ctx) => {
    res.setHeader("content-type", "application/json");
    res.end(JSON.stringify(search(ctx.body)));
  },
  { cache: new QueryCache() },
).listen(3000);

How the caching stays safe

The whole safety argument rests on one asymmetry:

  • A false miss (two equal bodies get different keys) is harmless — you recompute.
  • A false hit (two different bodies share a key) is a correctness/security bug — one client sees another's result. RFC 10008 Security Considerations flags exactly this.

So every normalization we apply is provably meaning-preserving, and when in doubt we normalize less:

| Content-Type | What we normalize | What we never do | | --------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | application/json, *+json | insignificant whitespace, object key order, string-escape form | merge numeric literals (10001e3); collapse big ints that lose precision; guess on duplicate keys (→ opaque) | | application/x-www-form-urlencoded | percent-encoding case, +/%20 | reorder pairs | | anything else (text, xml, octet-stream) | nothing — treated as opaque | cache by default (opt in with cacheOpaqueBodies, still exact-byte only) |

Keys are length-prefixed, domain-separated, and SHA-256 hashed so no concatenation of URL/type/body can forge a collision. The engine also honors Cache-Control (no-store/private/max-age), cacheable status codes (RFC 9111), and ETag/If-None-Match conditional requests.

Accept-Query negotiation (RFC 10008 §3)

import { advertiseAcceptQuery, negotiateQueryType } from "http-queryable";

res.setHeader("Accept-Query", advertiseAcceptQuery(["application/json", "application/sql"]));
const chosen = negotiateQueryType(req.headers["accept-query"], ["application/json"]);

API surface

  • Core (http-queryable): deriveCacheKey, normalizeBody, canonicalizeJson, validateQueryRequest, advertiseAcceptQuery, parseAcceptQuery, negotiateQueryType, QueryCache, MemoryStore, CacheStore, isQueryMethodSupported, assertQueryMethodSupported.
  • /express: queryable(), ensureQueryMethod, sendAcceptQuery, contentLocation, rejectInvalidQuery.
  • /fastify: fastifyQueryable plugin (+ reply.acceptQuery, reply.contentLocation).
  • /http: createQueryServer, createQueryListener.
  • /client: query, discoverAcceptQuery.

Custom cache store

class RedisStore {
  /* get/set/delete/clear */
}
new QueryCache({ store: new RedisStore() });

http-queryable ships an in-memory LRU + the CacheStore interface — plug in Redis/Memcached/etc. We deliberately don't ship a cache backend; the value is the correct body-aware key.

Roadmap (future, not in v0.1)

  • Edge/CDN pass-through guidance and adapters.
  • GraphQL-over-QUERY integration.
  • Additional structured body types (XML/CBOR) with conservative canonicalization.

Contributing & community

Contributions are welcome — bug fixes, adapters, and conservative new body normalizers especially. Please read:

  • CONTRIBUTING.md — dev setup, evals-first testing, Conventional Commits, branch naming, and the DCO sign-off.
  • CODE_OF_CONDUCT.md — Contributor Covenant 2.1.
  • SECURITY.mdreport vulnerabilities privately, never in a public issue. Cache-key collisions are treated as security bugs.

Bugs and features go through the issue forms. See the changelog for release history.

License

MIT © http-queryable contributors