qs is the query-string parser behind Express's extended query parsing and a huge amount of HTTP-handling code. It's careful, spec-faithful, and security-hardened — it has to be, because it parses untrusted input. That carefulness also means it does a lot of work on every call, even for the boring, common case of a=1&b=2&c=3.
qs-fast is a drop-in replacement that fast-paths that common case — 2.3× faster parse, 3.8× faster stringify — with byte-for-byte identical output, and no loss of qs's safety behavior.
Fast-path the easy case, fall back for everything else
The core idea: qs-fast handles the default-options path with a hand-rolled parser and stringifier, and transparently bails to real qs the moment anything non-trivial shows up:
- non-default options (custom delimiter,
allowDots, custom charset, …) - deep nesting (
depth > 5) -
__proto__/ prototype-pollution vectors - array index overflow
- cyclic object input
- anything else outside the fast lane
So you never trade away qs's correctness or its security hardening — you just skip its overhead when it's provably safe to. qs is a runtime dependency, and encoding goes through qs's own qs/lib/utils so the bytes match exactly.
// before
import qs from "qs";
// after
import qs from "@libs-jd/qs-fast";
npm i @libs-jd/qs-fast
The benchmark
Measured on Node v24 / V8, best-of-7, against qs@6 — every case checked for identical output first:
| op | qs | qs-fast | speedup |
|---|---|---|---|
stringify (typical objects) |
0.0277 ms | 0.0072 ms | 3.8× |
parse (typical query strings) |
0.0634 ms | 0.0275 ms | 2.3× |
In the correctness gate, the fast path covered 96% of parse and 100% of stringify workloads; the rest fell through to real qs and still returned identical results.
The part that matters: identical output
Same rule as any honest optimization — prove it before you trust it. qs-fast ships a 142-case gate (nested objects, arrays, encoded characters, edge cases, and the fallback triggers themselves) asserting the output is identical to real qs. 0 mismatches, run in CI on every change. When in doubt, it defers to qs, so the failure mode is "as fast as qs," never "wrong."
Links
MIT licensed. If you parse a lot of query strings, swap the import and see.
Top comments (1)
"I'm impressed by the performance gains qs-fast achieves, especially considering the identical output. Have you explored how qs-fast handles edge cases like nested query parameters, and if there are any plans to integrate it into Express's default query parsing?"