Skip to content

Instantly share code, notes, and snippets.

@thom-nic
Last active May 6, 2025 15:21
Show Gist options
  • Save thom-nic/361cc4bb7e7d0d3ee7543b5f774b5286 to your computer and use it in GitHub Desktop.
Save thom-nic/361cc4bb7e7d0d3ee7543b5f774b5286 to your computer and use it in GitHub Desktop.
Monkey-patch for ExpressJS v5 to allow `req.query` to be mutable by middleware
/* eslint-disable no-invalid-this */
const { request: Request } = require('express');
const parseQuery = Object.getOwnPropertyDescriptor(Request, 'query').get;
function getQuery() {
if (this.__queryModified)
return this.__queryModified;
return parseQuery.call(this);
}
function setQuery(value) {
this.__queryModified = Object.freeze(value);
}
// Change the express.request class definition. This only needs to be called once and could have
// side effects if other code is using the request class and expecting `query` to not be mutable.
function monkeyPatchExpress5RequestQuery() {
Object.defineProperty(Request, 'query', {
configurable: true,
enumerable: true,
get: getQuery,
set: setQuery,
});
};
// Middleware solution provided by 'zecat' from https://stackoverflow.com/a/79604142/213983
// This only patches the instance instead of modifying the class definition. It probably
// comes with a performance penalty since each instance is modified individually.
// This could be combined with the validation middleware, so that requests are only patched *if*
// query validation is performed. If there is a performance impact, it would be limited to
// routes where query validation is necessary, assuming not every route performs query validation.
function writableRequestQueryMiddleware(req, _res, next) {
Object.defineProperty(
req,
'query',
{
...Object.getOwnPropertyDescriptor(req, 'query'),
value: req.query,
writable: true,
});
next();
}
module.exports = {
monkeyPatchExpressRequestQuery,
writableRequestQueryMiddleware,
};
// Trivial example ExpressJS v5 validator using Joi and Boom.
// There are many better ways to do this e.g. as middleware, supporting `body` and `params` etc.
function validateQuery(schema, req, next) {
try {
// this assumes we used either monkeyPatchExpress5RequestQuery() to patch the class, or writableRequestQueryMiddleware() earlier
// in the middleware pipeline to make req.query mutable
req.query = Joi.attempt(req.query, schema);
return true; // valid
}
catch (err) {
// `error` is a Joi ValidationError. You could extract `err.details` and
// put them in your Boom payload.
// See: https://joi.dev/api/?v=17.13.3#validationerror
// See: https://hapi.dev/module/boom/api/?v=10.0.1
next(Boom.conflict('validation error', {data: err.details}));
return false; // invalid
}
}
// This combines the above two examples, and only modifies req.query when it must change due to validation
// req.query remains immutable after changing it here.
// update req.query while keeping the property immutable afterwards
function updateQuery(req, value) {
Object.defineProperty(
req,
'query',
{
...Object.getOwnPropertyDescriptor(req, 'query'),
writable: false,
value,
});
}
// this validator does not require any other middleware or monkey-patching.
// req.query will be updated to the value returned by Joi.attempt() but req.query will
// still be immutable after.
function validateQuery(schema, req, next) {
try {
updateQuery(req, Joi.attempt(req.query, schema));
return true; // valid
}
catch (err) {
// `error` is a Joi ValidationError. You could extract `err.details` and
// put them in your Boom payload.
// See: https://joi.dev/api/?v=17.13.3#validationerror
// See: https://hapi.dev/module/boom/api/?v=10.0.1
next(Boom.conflict('validation error', {data: err.details}));
return false; // invalid
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment