From 64896135648b04100ded52fc6599fd89788e1c36 Mon Sep 17 00:00:00 2001 From: Yan Date: Thu, 16 Oct 2025 19:34:47 -0700 Subject: [PATCH 1/3] fix: merge req.body/query/params/headers into decoded request --- packages/express-wrapper/src/middleware.ts | 8 ++++- .../express-wrapper/test/middleware.test.ts | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/express-wrapper/src/middleware.ts b/packages/express-wrapper/src/middleware.ts index 947760f7..3b00023f 100644 --- a/packages/express-wrapper/src/middleware.ts +++ b/packages/express-wrapper/src/middleware.ts @@ -216,5 +216,11 @@ export async function runMiddlewareChainIgnoringResults< } }); } - return input; + return { + ...(req as any).decoded, + ...(req.body || {}), + ...(req.query || {}), + ...(req.params || {}), + ...(req.headers || {}) + }; } diff --git a/packages/express-wrapper/test/middleware.test.ts b/packages/express-wrapper/test/middleware.test.ts index 397f4e03..938b3938 100644 --- a/packages/express-wrapper/test/middleware.test.ts +++ b/packages/express-wrapper/test/middleware.test.ts @@ -40,6 +40,38 @@ test('should handle errors passed to next()', async () => { ); }); +test('middleware that modifies req should pass changes through chain', async () => { + const testReq = { body: {}, headers: {} } as express.Request; + const testRes = {} as express.Response; + + const modifyReqMiddleware: express.RequestHandler = (req, _res, next) => { + (req as any).userId = 123; + next(); + }; + + const checkReqMiddleware: express.RequestHandler = (req, _res, next) => { + assert.equal( + (req as any).userId, + 123, + 'userId should still be on req in second middleware', + ); + next(); + }; + + await runMiddlewareChain( + { foo: 'test' }, + [modifyReqMiddleware, noopMiddleware, checkReqMiddleware], + testReq, + testRes, + ); + + assert.equal( + (testReq as any).userId, + 123, + 'req should still have userId after chain', + ); +}); + test('should work with middleware that return values', async () => { const result = await runMiddlewareChain( { foo: 'test' }, From ee2e4c14ce514aa311276e8d743428107096903d Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 17 Oct 2025 13:15:11 -0700 Subject: [PATCH 2/3] test: verify middleware req.body/query/params/headers modifications reach handler --- packages/express-wrapper/src/middleware.ts | 12 ++-- .../express-wrapper/test/middleware.test.ts | 32 ---------- packages/express-wrapper/test/server.test.ts | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/packages/express-wrapper/src/middleware.ts b/packages/express-wrapper/src/middleware.ts index 3b00023f..30c12e63 100644 --- a/packages/express-wrapper/src/middleware.ts +++ b/packages/express-wrapper/src/middleware.ts @@ -202,7 +202,7 @@ export async function runMiddlewareChainIgnoringResults< Input, Chain extends MiddlewareChain, >( - input: Input, + _input: Input, chain: Chain, req: express.Request, res: express.Response, @@ -216,11 +216,11 @@ export async function runMiddlewareChainIgnoringResults< } }); } - return { - ...(req as any).decoded, - ...(req.body || {}), - ...(req.query || {}), + return { + ...(req as any).decoded, + ...(req.body || {}), + ...(req.query || {}), ...(req.params || {}), - ...(req.headers || {}) + ...(req.headers || {}), }; } diff --git a/packages/express-wrapper/test/middleware.test.ts b/packages/express-wrapper/test/middleware.test.ts index 938b3938..397f4e03 100644 --- a/packages/express-wrapper/test/middleware.test.ts +++ b/packages/express-wrapper/test/middleware.test.ts @@ -40,38 +40,6 @@ test('should handle errors passed to next()', async () => { ); }); -test('middleware that modifies req should pass changes through chain', async () => { - const testReq = { body: {}, headers: {} } as express.Request; - const testRes = {} as express.Response; - - const modifyReqMiddleware: express.RequestHandler = (req, _res, next) => { - (req as any).userId = 123; - next(); - }; - - const checkReqMiddleware: express.RequestHandler = (req, _res, next) => { - assert.equal( - (req as any).userId, - 123, - 'userId should still be on req in second middleware', - ); - next(); - }; - - await runMiddlewareChain( - { foo: 'test' }, - [modifyReqMiddleware, noopMiddleware, checkReqMiddleware], - testReq, - testRes, - ); - - assert.equal( - (testReq as any).userId, - 123, - 'req should still have userId after chain', - ); -}); - test('should work with middleware that return values', async () => { const result = await runMiddlewareChain( { foo: 'test' }, diff --git a/packages/express-wrapper/test/server.test.ts b/packages/express-wrapper/test/server.test.ts index 03461f2e..8e573f5c 100644 --- a/packages/express-wrapper/test/server.test.ts +++ b/packages/express-wrapper/test/server.test.ts @@ -276,3 +276,66 @@ test('should return a 400 when request fails to decode', async () => { assert(response.body.error.startsWith('Invalid value undefined supplied to')); }); + +test('middleware that modifies req.body should reach handler even without routeHandler()', async () => { + const PostWithData = httpRoute({ + path: '/data', + method: 'POST', + request: httpRequest({ + body: { + originalField: t.string, + addedByMiddleware: optional(t.string), + }, + }), + response: { + 200: t.type({ + originalField: t.string, + addedByMiddleware: optional(t.string), + }), + }, + }); + + const testApiSpec = apiSpec({ + 'test.route': { + post: PostWithData, + }, + }); + + const modifyBodyMiddleware: express.RequestHandler = (req, _res, next) => { + req.body.addedByMiddleware = 'ADDED'; + next(); + }; + + const handler = async (params: { + originalField: string; + addedByMiddleware?: string; + }) => { + return { + type: 200, + payload: { + originalField: params.originalField, + addedByMiddleware: params.addedByMiddleware, + }, + } as const; + }; + + const app = createServer(testApiSpec, (app: express.Application) => { + app.use(express.json()); + return { + 'test.route': { + post: { middleware: [modifyBodyMiddleware], handler }, + }, + }; + }); + + const response = await supertest(app) + .post('/data') + .send({ originalField: 'test' }) + .expect(200); + + assert.equal( + response.body.addedByMiddleware, + 'ADDED', + 'addedByMiddleware should be present because req.body is part of req.decoded', + ); +}); From 220b32df168e91d54069e32bff618bd25bca47c0 Mon Sep 17 00:00:00 2001 From: Yan Date: Mon, 20 Oct 2025 10:38:20 -0700 Subject: [PATCH 3/3] docs: clarify runMiddlewareChainIgnoringResults now merges standard Express properties --- packages/express-wrapper/src/middleware.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/express-wrapper/src/middleware.ts b/packages/express-wrapper/src/middleware.ts index 30c12e63..3d9e7a8b 100644 --- a/packages/express-wrapper/src/middleware.ts +++ b/packages/express-wrapper/src/middleware.ts @@ -189,14 +189,19 @@ export async function runMiddlewareChain< } /** - * Runs a middleware chain, but does not modify the decoded request (input) with properties from any middleware. + * Runs a middleware chain and merges standard Express properties (body, query, params, headers) with the decoded request. + * Does not incorporate other modifications middleware may make to the request object. * This primarily exists to preserve backwards-compatible behavior for RouteHandlers defined without the `routeHandler` function. * - * @param input - the decoded request properties (just passed through) + * Note: The name "IgnoringResults" refers to ignoring typed middleware return values (via middlewareFn), + * NOT ignoring standard Express request modifications. Middleware can still modify req.body/query/params/headers + * and those changes WILL be included in the returned object. + * + * @param _input - the decoded request properties (unused, decoded data is read from req) * @param chain - the middleware chain * @param req - express request object * @param res - express response object - * @returns `input` unmodified + * @returns decoded request merged with req.body, req.query, req.params, and req.headers */ export async function runMiddlewareChainIgnoringResults< Input,