fix(node): ignore req.body that express 4's body-parser assigns without parsing - #102
Conversation
…ut parsing
body-parser 1.x (express 4) sets `req.body = {}` on every request, even the
ones it leaves unread, so file, form-data, url-search-params, octet-stream and
event-stream bodies all resolved to `{}`, and a bodyless request gave `{}`
instead of `undefined`. Only trust `req.body` once the request stream has been
consumed.
Adds express 4 to the integration test matrix.
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Merging this PR will not alter performance
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Node body short-circuit —
toStandardBodynow trusts an upstreamreq.bodyonly when!req.readable, so express 4's body-parser{}placeholder is ignored and the stream is parsed instead. - Unit test — the existing
prefer parsed bodycase now consumes the stream first, and a new case asserts areq.body = {}set without consuming is ignored for both a file upload and a bodyless GET. - Express 4 integration matrix —
createExpressjsClientServerTestgainsversion?: 4 | 5; express 4 (with and without the body parser) is added to the data-transfer and signal/cancel matrices. - Dev dependencies — aliased
express4/@types/express4and the corresponding lockfile entries.
I confirmed the premise empirically (Node 24, express 4.22.3 and 5.2.1): body-parser 1.x sets req.body = {} with readable === true for content types it leaves unread, while after parsing JSON it sets readable === false; body-parser 2.x leaves req.body undefined. Node's readable is true before any read (even bodyless GET) and false only after full consumption, so !req.readable is a correct proxy for "consumed" on these paths. Reverting the guard makes the expressjs4-body-parser integration cases fail, so the new coverage is real. Local runs: body.test.ts 35 pass, data-transfer expressjs 64 pass, signal-and-cancel expressjs 68 pass, root tsc and eslint on the changed files clean.
The intentional behavior change (ignore a req.body whose stream was never read) is the point of the fix and is already documented in the PR body. No action needed.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

Under Express 4,
express.json()(body-parser 1.x) setsreq.body = {}on every request, even ones it never reads. Because of that,toStandardBodyreturned{}for file uploads, FormData, URLSearchParams, octet-streams and event-streams, and for a bodyless GET it returned{}where it should returnundefined. The adapter now usesreq.bodyonly once the request stream has been consumed. Express 4 apps get the correct bodies back, and Express 5 behaves as before.Fixes
express.json(), non-JSON request bodies are parsed correctly again instead of resolving to{}undefinedagainBehavior change
req.bodyis set on a request whose stream nobody has read, the adapter now ignores it and parses the stream itself.Testing
req.bodythat was assigned but never read, covering a file upload and a bodyless GETexpress4and@types/express4as aliased dev dependenciestsc -band eslint pass