querry-cleaner: Express Middleware to Fix Duplicate Query Parameters

By Harshcodez 2 min read Markdown (.md) JSON (.json)
A zero-dependency Express middleware that removes duplicate URL query parameters, preventing HTTP Parameter Pollution bugs and keeping your API responses predictable.

Querry-Cleaner — Fixing Duplicate Query Parameters in Express

Duplicate query parameters are a real problem. A request like /search?page=1&sort=name&page=2 can cause inconsistent behavior — some frameworks return the first value, some return the last, and some return an array where your code expects a string.

This is known as HTTP Parameter Pollution, and it's both a bug source and a security concern.

querry-cleaner is a zero-dependency Express middleware that deduplicates query parameters before they reach your route handlers.

Install

npm install querry-cleaner

Usage

import express from "express";
import queryCleaner from "querry-cleaner";

const app = express();
app.use(queryCleaner());

app.get("/search", (req, res) => {
  // req.url is already clean — no duplicate keys
  res.json({ query: req.query });
});

app.listen(3000);

That's it. One line to add, zero config needed.

Before & After

Incoming request What your handler sees
/search?page=1&sort=name&page=2 /search?page=2&sort=name
/api?token=abc&token=xyz /api?token=xyz

The last value wins — consistent, predictable behavior.

Whitelist

Need certain routes to stay untouched? Pass a whitelist:

app.use(
  queryCleaner({
    whitelist: ["/webhooks/stripe", "/health"],
  })
);

Whitelisted paths skip cleaning entirely.

How It Works

The core logic is ~15 lines. It uses the built-in URL and URLSearchParams APIs — no external dependencies.

const parsedUrl = new URL(req.url, "http://localhost");
const cleanParams = new URLSearchParams();

parsedUrl.searchParams.forEach((value, key) => {
  cleanParams.set(key, value); // .set() overwrites, .append() would keep both
});

req.url = parsedUrl.pathname + "?" + cleanParams.toString();

The trick is .set() vs .append() — calling .set() on an existing key overwrites it, so only the last value survives. The middleware rebuilds req.url with the cleaned params before calling next().

Links