chore: update express and webpack-dev-server#7443
Conversation
📝 WalkthroughWalkthroughThis PR bumps Express (and matching types) to v5.2.1 across e2e and example manifests, changes server startup callbacks to an error-first signature that throws on listen failures, and replaces wildcard catch-all routes with explicit splat patterns in SSR/fallback handlers. ChangesExpress v5.2.1 upgrade and error-handling integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
View your CI Pipeline Execution ↗ for commit 2efb369
☁️ Nx Cloud last updated this comment at |
🚀 Changeset Version Preview9 package(s) bumped directly, 15 bumped as dependents. 🟨 Minor bumps
🟩 Patch bumps
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/react-start/custom-server-rsbuild/express-server.ts`:
- Around line 60-63: The async callback passed to app.listen causes thrown
errors and rejected awaits to become unhandled promise rejections; change the
startup to use a non-async callback and chain devServer.afterListen() with
.then().catch() (or call devServer.afterListen().then(() => { console.log...
}).catch(err => { processLogger.error/console.error(err); process.exit(1); }))
so errors from devServer.afterListen() and the listen call are caught and the
process exits on failure; update the code surrounding httpServer, app.listen,
devServer.afterListen, and PORT to remove the async modifier and add explicit
.then/.catch error handling.
In `@examples/solid/basic-ssr-streaming-file-based/server.js`:
- Around line 100-101: The console message hardcodes 3000 instead of the actual
port returned by getPort; fix by resolving the port before calling app.listen
(e.g., const port = await getPort({ port: portNumbers(3000, 3100) })), pass that
port into app.listen(port, ...), and update the console.info call to
interpolate/use that port variable (e.g., use port in the URL string) while
keeping the existing error callback (error => { if (error) throw error }) as-is;
update references to getPort, portNumbers, app.listen, and the console.info call
accordingly.
In `@examples/solid/with-trpc/src/server/server.ts`:
- Around line 81-82: Replace the current use of app.listen(PORT, (error) => { if
(error) throw error } ) with code that captures the returned server instance
from app.listen(PORT) and attaches an 'error' event handler to that server to
handle startup failures; specifically, stop throwing from the listen callback
and instead call app.listen(PORT) to get the server object, then register
server.on('error', (err) => { /* log/handle err */ }) (you can also add
server.on('listening', ...) or a callback that only logs success) so startup
errors are handled via the server's 'error' event rather than throwing inside
the listen callback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 513883fa-1ba5-4a4d-83e5-5e3f062cb940
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (34)
e2e/react-start/basic/package.jsone2e/react-start/basic/server.jse2e/react-start/css-inline/package.jsone2e/react-start/css-inline/server.jse2e/react-start/custom-basepath/express-server.tse2e/react-start/custom-basepath/package.jsone2e/react-start/custom-server-rsbuild/express-server.tse2e/react-start/custom-server-rsbuild/package.jsone2e/react-start/split-base-and-basepath/package.jsone2e/react-start/split-base-and-basepath/prod-server.jse2e/react-start/transform-asset-urls/package.jsone2e/react-start/transform-asset-urls/tests/cdn-server.mjse2e/solid-start/basic/package.jsone2e/solid-start/basic/server.jse2e/solid-start/custom-basepath/express-server.tse2e/solid-start/custom-basepath/package.jsone2e/vue-start/basic/package.jsone2e/vue-start/basic/server.jse2e/vue-start/custom-basepath/express-server.tse2e/vue-start/custom-basepath/package.jsonexamples/react/basic-ssr-file-based/package.jsonexamples/react/basic-ssr-file-based/server.jsexamples/react/basic-ssr-streaming-file-based/package.jsonexamples/react/basic-ssr-streaming-file-based/server.jsexamples/react/with-trpc-react-query/package.jsonexamples/react/with-trpc-react-query/src/server/server.tsexamples/react/with-trpc/package.jsonexamples/react/with-trpc/src/server/server.tsexamples/solid/basic-ssr-file-based/package.jsonexamples/solid/basic-ssr-file-based/server.jsexamples/solid/basic-ssr-streaming-file-based/package.jsonexamples/solid/basic-ssr-streaming-file-based/server.jsexamples/solid/with-trpc/package.jsonexamples/solid/with-trpc/src/server/server.ts
| const httpServer = app.listen(PORT, async (error) => { | ||
| if (error) throw error | ||
| await devServer.afterListen() | ||
| console.log(`Server is running on http://localhost:${PORT}`) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify async app.listen callbacks that throw in this repo.
rg -nP --type=ts -C2 'app\.listen\([^,]+,\s*async\s*\([^)]*\)\s*=>\s*\{'
rg -nP --type=ts -C2 'if\s*\(\s*error\s*\)\s*throw\s+error'Repository: TanStack/router
Length of output: 4270
🏁 Script executed:
cat -n e2e/react-start/custom-server-rsbuild/express-server.tsRepository: TanStack/router
Length of output: 3022
Handle promise rejections in the async app.listen callback.
Line 60 uses an async callback with app.listen, where throw error at line 61 becomes an unhandled Promise rejection instead of a synchronous startup failure. Additionally, the await devServer.afterListen() at line 62 could reject without being caught. Remove the async modifier and use explicit .then().catch() chaining to ensure errors are handled and the process exits on failure.
Proposed fix
- const httpServer = app.listen(PORT, async (error) => {
- if (error) throw error
- await devServer.afterListen()
- console.log(`Server is running on http://localhost:${PORT}`)
- })
+ const httpServer = app.listen(PORT, (error) => {
+ if (error) throw error
+ devServer
+ .afterListen()
+ .then(() => {
+ console.log(`Server is running on http://localhost:${PORT}`)
+ })
+ .catch((afterListenError) => {
+ console.error(afterListenError)
+ process.exit(1)
+ })
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const httpServer = app.listen(PORT, async (error) => { | |
| if (error) throw error | |
| await devServer.afterListen() | |
| console.log(`Server is running on http://localhost:${PORT}`) | |
| const httpServer = app.listen(PORT, (error) => { | |
| if (error) throw error | |
| devServer | |
| .afterListen() | |
| .then(() => { | |
| console.log(`Server is running on http://localhost:${PORT}`) | |
| }) | |
| .catch((afterListenError) => { | |
| console.error(afterListenError) | |
| process.exit(1) | |
| }) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/react-start/custom-server-rsbuild/express-server.ts` around lines 60 -
63, The async callback passed to app.listen causes thrown errors and rejected
awaits to become unhandled promise rejections; change the startup to use a
non-async callback and chain devServer.afterListen() with .then().catch() (or
call devServer.afterListen().then(() => { console.log... }).catch(err => {
processLogger.error/console.error(err); process.exit(1); })) so errors from
devServer.afterListen() and the listen call are caught and the process exits on
failure; update the code surrounding httpServer, app.listen,
devServer.afterListen, and PORT to remove the async modifier and add explicit
.then/.catch error handling.
| app.listen(await getPort({ port: portNumbers(3000, 3100) }), (error) => { | ||
| if (error) throw error |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Express 5 / Node.js, does app.listen callback receive an error argument, or are listen failures emitted on the server "error" event?
💡 Result:
In Express 5, the callback you pass to app.listen(...) can receive an error as its first argument when the server emits an error during the bind/listen step (e.g., EADDRINUSE). Express 5 no longer throws those listen-time errors by default; instead it invokes your callback and passes the error to it. [1] Therefore: listen failures are handled via the callback error argument in Express 5, while the underlying mechanism is that the server emits an 'error' event (Express wires that into the callback). [1] In Express 4, such errors would be thrown instead of being delivered to the callback. [1]
Citations:
🏁 Script executed:
#!/bin/bash
# Find and examine the package.json in the example directory
find examples/solid/basic-ssr-streaming-file-based -name "package.json" -type f | head -5Repository: TanStack/router
Length of output: 118
🏁 Script executed:
#!/bin/bash
# Get the full context of server.js around lines 100-101
wc -l examples/solid/basic-ssr-streaming-file-based/server.jsRepository: TanStack/router
Length of output: 119
🏁 Script executed:
#!/bin/bash
# Read the server.js file with line numbers, focusing on the area around lines 100-101
sed -n '95,110p' examples/solid/basic-ssr-streaming-file-based/server.js | cat -nRepository: TanStack/router
Length of output: 398
🏁 Script executed:
#!/bin/bash
# Read package.json to check Express version
cat examples/solid/basic-ssr-streaming-file-based/package.jsonRepository: TanStack/router
Length of output: 997
🌐 Web query:
Express 5.0 app.listen error handling callback vs server error event
💡 Result:
In Express 5, startup/listen errors are delivered to the app.listen callback argument (if you provided one), rather than being thrown immediately as they were in Express 4. Express 5 documentation states: “app.listen … will invoke the user-provided callback function (if provided) when the server receives an error event … If there is an error, it will be passed to the callback as an argument.” [1] So which should you use? 1) app.listen callback for listen/startup failures (e.g., EADDRINUSE) Use the callback you pass to app.listen(port, host, (err) => …) to handle “server emits error during listen” cases. Express 5’s migration guide shows checking the first callback argument and throwing/logging it when it’s present. [1] 2) server error event (Node http server) for errors emitted by the underlying server Express’s app.listen is a convenience wrapper around Node’s http.Server.listen. [2] That means the underlying HTTP server is still the real object, and Node error semantics are based on EventEmitter’s ‘error’ event. [3][2] However, with Express 5 specifically, the framework has changed so that listen-time errors are routed into the app.listen callback (when provided). [1] Practical guidance (Express 5.0) - Prefer passing a callback to app.listen for startup/listen errors, and check the callback’s error argument. [1] - For other server-side errors that happen after the server is already listening, you would still typically attach an ‘error’ handler to the underlying server (or otherwise ensure ‘error’ events are handled), consistent with Node’s EventEmitter behavior. [3] Example (listen-time error via callback) const server = app.listen(8080, '0.0.0.0', (error) => { if (error) { // e.g. EADDRINUSE console.error(error); return; } console.log(Listening on ${JSON.stringify(server.address())}); }); [1] Key distinction - “Express request handling errors” (route/middleware) are handled via Express error-handling middleware (next(err) / async rejection forwarding, etc.). [4] - “Server listen/startup errors” are handled via the app.listen callback in Express 5. [1]
Citations:
- 1: https://expressjs.com/en/guide/migrating-5.html
- 2: https://expressjs.com/en/5x/api.html
- 3: https://nodejs.org/docs/latest/api/errors.html
- 4: https://expressjs.com/en/guide/error-handling.html
Console message hardcodes port instead of using the dynamically allocated port.
The actual port is selected by getPort() on line 100, but the console.info message on line 101 hardcodes 3000. If the port falls back to a different value (e.g., 3001), the user sees incorrect information. Use the port variable in the message instead.
Regarding the error handling: The current callback pattern is correct for Express 5. According to Express 5 migration documentation, app.listen() routes startup/listen errors (like EADDRINUSE) into the callback's error argument—the pattern (error) => { if (error) throw error } is the intended way to handle bind failures in Express 5.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/solid/basic-ssr-streaming-file-based/server.js` around lines 100 -
101, The console message hardcodes 3000 instead of the actual port returned by
getPort; fix by resolving the port before calling app.listen (e.g., const port =
await getPort({ port: portNumbers(3000, 3100) })), pass that port into
app.listen(port, ...), and update the console.info call to interpolate/use that
port variable (e.g., use port in the URL string) while keeping the existing
error callback (error => { if (error) throw error }) as-is; update references to
getPort, portNumbers, app.listen, and the console.info call accordingly.
| app.listen(PORT, (error) => { | ||
| if (error) throw error |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For Express 5 + Node HTTP server, what is the correct way to handle app.listen startup failures (EADDRINUSE/EACCES)? Is an error argument passed to the listen callback?
💡 Result:
In Express 5, the behavior of app.listen has changed compared to Express 4 regarding error handling [1]. While Express 4 typically did not pass startup errors to the callback, Express 5 explicitly invokes the user-provided callback with an error argument if the server fails to start (e.g., due to EADDRINUSE or EACCES) [1]. To correctly handle startup failures in Express 5, you should check for the presence of an error argument in the callback function [1]: const server = app.listen(port, (error) => { if (error) { console.error('Failed to start server:', error); process.exit(1); // Handle the error appropriately } console.log('Server is listening on port', port); }); Alternatively, because app.listen returns the underlying Node.js http.Server instance, you can attach an error event listener directly to the server object [2][3]. This approach is robust and works consistently across different versions of Express and Node.js [2][4]: const server = app.listen(port); server.on('error', (error) => { if (error.code === 'EADDRINUSE') { console.error(Port ${port} is already in use.); } else { console.error('Server error:', error); } process.exit(1); }); Using the 'error' event is often preferred for complex startup logic, such as implementing retry mechanisms or logging specific error codes like EADDRINUSE or EACCES [2][4][5]. Note that if you do not handle the 'error' event, Node.js will emit an unhandled exception, which typically causes the process to crash [6].
Citations:
- 1: app.listen() error behavior is different in Express 4 vs. 5, if port is occupied expressjs/express#6191
- 2: EADDRINUSE code in express expressjs/express#4808
- 3: https://stackoverflow.com/questions/13322876/node-js-express-app-handle-startup-errors
- 4: https://docs.deno.com/api/node/net/~/Server.prototype.listen
- 5: https://oneuptime.com/blog/post/2026-01-25-fix-eaddrinuse-nodejs/view
- 6: https://stackoverflow.com/questions/78500404/error-listen-eaddrinuse-address-already-in-use
🏁 Script executed:
# Find package.json in the project to check Express version
fd -t f package.json | head -5Repository: TanStack/router
Length of output: 222
🏁 Script executed:
# Examine the server.ts file to see the full context around lines 81-82
if [ -f "examples/solid/with-trpc/src/server/server.ts" ]; then
echo "=== File exists, checking full context ==="
wc -l "examples/solid/with-trpc/src/server/server.ts"
echo ""
echo "=== Lines 70-90 context ==="
sed -n '70,90p' "examples/solid/with-trpc/src/server/server.ts"
else
echo "File not found at specified path"
find . -name "server.ts" -type f | grep -E "(solid|trpc)" | head -10
fiRepository: TanStack/router
Length of output: 614
🏁 Script executed:
# Check the package.json in the examples/solid/with-trpc directory for Express version
if [ -f "examples/solid/with-trpc/package.json" ]; then
echo "=== package.json found ==="
cat "examples/solid/with-trpc/package.json" | grep -A 5 -B 5 "express"
else
echo "Checking for package.json"
find examples/solid/with-trpc -name "package.json" -type f
fiRepository: TanStack/router
Length of output: 557
🏁 Script executed:
cat -n "examples/solid/with-trpc/src/server/server.ts"Repository: TanStack/router
Length of output: 2978
🏁 Script executed:
# Check TypeScript configuration for strict mode
fd -t f tsconfig.json | xargs grep -l "strict" 2>/dev/null | head -3Repository: TanStack/router
Length of output: 185
🏁 Script executed:
# Verify the typing of Express app.listen in Express 5
rg "listen" examples/solid/with-trpc/src/server/server.ts -B 3 -A 3Repository: TanStack/router
Length of output: 249
Use the server's 'error' event for startup failures instead of relying on the listen callback.
While Express 5 does pass error arguments to the listen callback, the idiomatic and more robust approach in Node.js is to attach an error handler to the returned server instance. This pattern works across Node.js versions and prevents potential unhandled promise rejections (the current code throws an error in a .then() without a .catch() handler).
Suggested fix
- createServer().then(({ app }) =>
- app.listen(PORT, (error) => {
- if (error) throw error
- console.info(`Server available at: http://localhost:${PORT}`)
- }),
- )
+ createServer().then(({ app }) => {
+ const server = app.listen(PORT, () => {
+ console.info(`Server available at: http://localhost:${PORT}`)
+ })
+ server.on('error', (error) => {
+ throw error
+ })
+ })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/solid/with-trpc/src/server/server.ts` around lines 81 - 82, Replace
the current use of app.listen(PORT, (error) => { if (error) throw error } ) with
code that captures the returned server instance from app.listen(PORT) and
attaches an 'error' event handler to that server to handle startup failures;
specifically, stop throwing from the listen callback and instead call
app.listen(PORT) to get the server object, then register server.on('error',
(err) => { /* log/handle err */ }) (you can also add server.on('listening', ...)
or a callback that only logs success) so startup errors are handled via the
server's 'error' event rather than throwing inside the listen callback.
There was a problem hiding this comment.
Important
At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.
Nx Cloud has identified a possible root cause for your failed CI:
We investigated the failing tanstack-react-start-e2e-split-base-and-basepath:test:e2e task and found that the Chromium headless shell is crashing with a SIGSEGV (General Protection Fault) during browser launch — entirely unrelated to the Express v5.2.1 upgrade, route pattern changes, or listen callback updates in this PR. Our recommendation is to re-run the task, as this is an environment infrastructure issue that no code change can address.
No code changes were suggested for this issue.
Trigger a rerun:
🎓 Learn more about Self-Healing CI on nx.dev
Summary
Testing
Summary by CodeRabbit
Chores
Bug Fixes