Skip to content

Commit 456d8e7

Browse files
ale-grossellealessandro-grosselle-adevintagraphite-app[bot]bjohansebastimneutkens
authored
fix: ensure req.query is writable (#81573)
### What? - Upgrade development dependency **Express** to the latest version (5.1.0). - Replace direct assignment of `apiReq.query` with `Object.defineProperty` to ensure the property is writable, enumerable, and configurable. ### Why? - To use the most up-to-date and supported version of Express instead of a legacy one. - To fix [expressjs/express#6633](expressjs/express#6633), where Express 5.x defines `req.query` as a read-only getter, preventing Next.js from overriding it directly. ### How? - Update the Express version in `package.json` and run `pnpm install` to apply the change. - Replace: ```js apiReq.query = query; ``` With ```js Object.defineProperty(apiReq, 'query', { value: { ...query }, writable: true, enumerable: true, configurable: true, }); ``` --------- Co-authored-by: Alessandro Grosselle <[email protected]> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Sebastian Beltran <[email protected]> Co-authored-by: Tim Neutkens <[email protected]>
1 parent 0b6fe15 commit 456d8e7

File tree

4 files changed

+78
-2
lines changed

4 files changed

+78
-2
lines changed

packages/next/src/server/api-utils/node/api-resolver.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,14 @@ export async function apiResolver(
355355

356356
// Parsing of cookies
357357
setLazyProp({ req: apiReq }, 'cookies', getCookieParser(req.headers))
358-
// Parsing query string
359-
apiReq.query = query
358+
// Ensure req.query is a writable, enumerable property by using Object.defineProperty.
359+
// This addresses Express 5.x, which defines query as a getter only (read-only).
360+
Object.defineProperty(apiReq, 'query', {
361+
value: { ...query },
362+
writable: true,
363+
enumerable: true,
364+
configurable: true,
365+
})
360366
// Parsing preview data
361367
setLazyProp({ req: apiReq }, 'previewData', () =>
362368
tryGetPreviewData(req, res, apiContext, !!apiContext.multiZoneDraftMode)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('api-resolver-query-writeable', () => {
4+
const { next, skipped } = nextTestSetup({
5+
files: __dirname,
6+
skipDeployment: true,
7+
startCommand: 'node server.js',
8+
serverReadyPattern: /Next mode: (production|development)/,
9+
dependencies: {
10+
'get-port': '5.1.1',
11+
express: '5.1.0',
12+
},
13+
})
14+
15+
if (skipped) {
16+
return
17+
}
18+
19+
it('should allow req.query to be writable and reflect changes made in the API handler', async () => {
20+
const res = await next.fetch('/api?hello=yes', {
21+
headers: {
22+
'Content-Type': 'application/json; charset=utf-8',
23+
},
24+
})
25+
if (!res.ok) {
26+
throw new Error('Fetch failed')
27+
}
28+
const data = await res.json()
29+
expect(data).toEqual({ query: { hello: 'yes', changed: 'yes' } })
30+
})
31+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default (req, res) => {
2+
req.query = { ...req.query, changed: 'yes' }
3+
res.status(200).json({ query: req.query })
4+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const next = require('next')
2+
const express = require('express')
3+
4+
const getPort = require('get-port')
5+
6+
async function main() {
7+
const dev = process.env.NEXT_TEST_MODE === 'dev'
8+
process.env.NODE_ENV = dev ? 'development' : 'production'
9+
10+
const port = await getPort()
11+
const app = next({ dev })
12+
const handleNextRequest = app.getRequestHandler()
13+
14+
await app.prepare()
15+
16+
const server = express()
17+
18+
server.all('/:path', (req, res) => {
19+
handleNextRequest(req, res)
20+
})
21+
22+
server.listen(port, (err) => {
23+
if (err) {
24+
throw err
25+
}
26+
27+
console.log(`- Local: http://localhost:${port}`)
28+
console.log(`- Next mode: ${dev ? 'development' : process.env.NODE_ENV}`)
29+
})
30+
}
31+
32+
main().catch((err) => {
33+
console.error(err)
34+
process.exit(1)
35+
})

0 commit comments

Comments
 (0)