From 54e70501ea856c81d7d5b3afe90c02aa7731c54c Mon Sep 17 00:00:00 2001 From: Lukas Elmer Date: Wed, 29 Oct 2025 23:18:25 +0100 Subject: [PATCH] Remove direct `qs` dependency - As suggested here: https://github.com/expressjs/express/discussions/5783#discussioncomment-14224233 - See also #6647, #5723, #6374, #3230, #3272, https://github.com/expressjs/express/pulls?q=is%3Apr+qs+is%3Aclosed - This doesn't remove `qs` from `body-parser` --- History.md | 7 +++++++ lib/utils.js | 20 ++------------------ package.json | 2 +- test/req.query.js | 15 ++++++++++++--- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/History.md b/History.md index 5b6cba51ed8..c2673e34b7c 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,10 @@ +unreleased +========== + +* Remove `app.set('query parser', 'extended')` option; users should pass a custom parser function instead + - To replicate old `'extended'` behavior: `app.set('query parser', str => require('qs').parse(str, { allowPrototypes: true }))` + - This allows removing `qs` from the dependencies for users who don't configure it explicitly + 5.1.0 / 2025-03-31 ======================== diff --git a/lib/utils.js b/lib/utils.js index 4f21e7ef1e3..be4d5f67a4f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -17,7 +17,6 @@ var contentType = require('content-type'); var etag = require('etag'); var mime = require('mime-types') var proxyaddr = require('proxy-addr'); -var qs = require('qs'); var querystring = require('node:querystring'); const { Buffer } = require('node:buffer'); @@ -154,7 +153,7 @@ exports.compileETag = function(val) { /** * Compile "query parser" value to function. * - * @param {String|Function} val + * @param {String|Function|Boolean} val * @return {Function} * @api private */ @@ -174,8 +173,7 @@ exports.compileQueryParser = function compileQueryParser(val) { case false: break; case 'extended': - fn = parseExtendedQueryString; - break; + throw new TypeError("query parser 'extended' is no longer supported; use: `app.set('query parser', str => qs.parse(str, { allowPrototypes: true }))` to replicate the old behavior"); default: throw new TypeError('unknown value for query parser function: ' + val); } @@ -255,17 +253,3 @@ function createETagGenerator (options) { return etag(buf, options) } } - -/** - * Parse an extended query string with qs. - * - * @param {String} str - * @return {Object} - * @private - */ - -function parseExtendedQueryString(str) { - return qs.parse(str, { - allowPrototypes: true - }); -} diff --git a/package.json b/package.json index db7661de46d..52cf3ed72dc 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", - "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", @@ -76,6 +75,7 @@ "morgan": "1.10.1", "nyc": "^17.1.0", "pbkdf2-password": "1.2.1", + "qs": "^6.14.0", "supertest": "^6.3.0", "vhost": "~3.0.2" }, diff --git a/test/req.query.js b/test/req.query.js index c0d3c8376e9..9ba1455d833 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -1,6 +1,7 @@ 'use strict' var assert = require('node:assert') +var qs = require('qs'); var express = require('../') , request = require('supertest'); @@ -22,9 +23,9 @@ describe('req', function(){ .expect(200, '{"user[name]":"tj"}', done); }); - describe('when "query parser" is extended', function () { + describe('when "query parser" is set to qs (formerly "extended")', function () { it('should parse complex keys', function (done) { - var app = createApp('extended'); + var app = createApp(qs.parse); request(app) .get('/?foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!') @@ -32,7 +33,7 @@ describe('req', function(){ }); it('should parse parameters with dots', function (done) { - var app = createApp('extended'); + var app = createApp(qs.parse); request(app) .get('/?user.name=tj') @@ -40,6 +41,14 @@ describe('req', function(){ }); }); + describe('when "query parser" is set to extended', function () { + it('should throw with a helpful error message', function () { + assert.throws(() => createApp('extended'), + new TypeError("query parser 'extended' is no longer supported; use: `app.set('query parser', str => qs.parse(str, { allowPrototypes: true }))` to replicate the old behavior") + ); + }); + }); + describe('when "query parser" is simple', function () { it('should not parse complex keys', function (done) { var app = createApp('simple');