Skip to content

feat: Migrate from Node.js 18 to Node.js 24 LTS#6

Open
devin-ai-integration[bot] wants to merge 1 commit into
devin/1777386487-node18-with-breaking-changesfrom
devin/1777395766-node24-upgrade
Open

feat: Migrate from Node.js 18 to Node.js 24 LTS#6
devin-ai-integration[bot] wants to merge 1 commit into
devin/1777386487-node18-with-breaking-changesfrom
devin/1777395766-node24-upgrade

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Summary

Migrates the TODO List app from Node.js 18 to Node.js 24 LTS (v24.15.0 — codename Krypton), fixing all breaking changes from removed/deprecated Node APIs and upgrading dependencies to their latest compatible versions.

Breaking Changes Fixed

File Deprecated API Replacement
src/utils/crypto.js crypto.createCipher / crypto.createDecipher crypto.createCipheriv / crypto.createDecipheriv with random IV
src/utils/crypto.js new Buffer() Buffer.from()
src/utils/encoding.js require('punycode') (removed built-in) Native String.normalize('NFKC')
src/utils/encoding.js new Buffer() Buffer.from()
src/utils/sanitizer.js url.parse() WHATWG new URL() API
src/utils/sanitizer.js querystring module URLSearchParams / decodeURIComponent
src/middleware/validators.js util._extend() Object.assign()
src/db/connection.js fs.R_OK / fs.W_OK fs.constants.R_OK / fs.constants.W_OK

Dependency Upgrades

Package Old Version New Version
better-sqlite3 ^9.6.0 ^12.9.0
express ^4.18.2 ^4.22.1
helmet ^7.1.0 ^8.1.0
supertest ^6.3.3 ^7.2.2
uuid ^9.0.0 ^11.1.0
express-rate-limit ^7.1.5 ^7.5.0
express-validator ^7.0.1 ^7.3.2
ejs ^3.1.9 ^3.1.10
compression ^1.7.4 ^1.8.0
nodemon ^3.0.2 ^3.1.14

Config Updates

  • .nvmrc: 18 → 24
  • package.json engines: >=18.0.0 <19.0.0>=24.0.0
  • .eslintrc.json env: es2021es2024
  • README.md: Updated all Node 18 references to Node 24

Review & Testing Checklist for Human

  • Verify the crypto.createCipheriv / crypto.createDecipheriv token generation/validation works correctly — the token format changed (now includes IV prefix), so any existing tokens from the old format will not be decodable
  • Run nvm use 24 && npm test locally to confirm all 53 tests pass on Node 24
  • Spot-check the sanitizeSearchQuery function with URL inputs to ensure the new URL() migration preserves the same behavior as the old url.parse()
  • Run npm start and verify the web UI loads and CRUD operations work end-to-end

Notes

  • ESLint was kept at v8 since migrating to ESLint v9 flat config is a separate effort
  • Express was kept at v4.x since Express 5 is a separate major migration with its own breaking changes
  • The better-sqlite3 upgrade from v9 to v12 was required because v9 cannot compile native addons against Node 24

Link to Devin session: https://app.devin.ai/sessions/aa96c67a5d46414fa2d8f5b35704cfd8
Requested by: @clivingston-cognition

…reaking changes

- Upgrade Node.js from 18.x to 24.x LTS (v24.15.0)
- Replace crypto.createCipher/createDecipher with createCipheriv/createDecipheriv
- Replace deprecated new Buffer() with Buffer.from() across all utils
- Replace deprecated require('punycode') with native String.normalize('NFKC')
- Replace deprecated url.parse() with WHATWG URL API (new URL())
- Replace deprecated querystring module with URLSearchParams/decodeURIComponent
- Replace deprecated util._extend() with Object.assign()
- Replace deprecated fs.R_OK/fs.W_OK with fs.constants.R_OK/fs.constants.W_OK
- Upgrade better-sqlite3 from 9.x to 12.x (required for Node 24 native addon support)
- Upgrade express from 4.18.x to 4.22.x
- Upgrade helmet from 7.x to 8.x
- Upgrade supertest from 6.x to 7.x
- Upgrade uuid from 9.x to 11.x
- Update all other dependencies to latest compatible versions
- Update .nvmrc, package.json engines, README.md, and .eslintrc.json
- All 53 existing tests continue to pass

Co-Authored-By: Chris Livingston <chris.livingston@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread .eslintrc.json
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 parserOptions.ecmaVersion not updated to match env: es2024

The ESLint env was updated from es2021 to es2024 (line 4), but parserOptions.ecmaVersion on line 9 was left at 2021. This means the ESLint parser will reject any syntax newer than ES2021 (e.g., using declarations, decorator syntax, etc.) even though ES2024 globals are made available by the env setting. Since parserOptions.ecmaVersion explicitly overrides the implicit ecmaVersion from env, the parser is more restrictive than intended for a Node.js 24 upgrade.

(Refers to line 9)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/utils/sanitizer.js
// Decode any percent-encoded characters using querystring
cleaned = querystring.unescape(cleaned);
// Decode any percent-encoded characters
cleaned = decodeURIComponent(cleaned);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 decodeURIComponent throws URIError on malformed percent-encoded input, unlike the replaced querystring.unescape

The migration from querystring.unescape(cleaned) to decodeURIComponent(cleaned) introduces an unhandled exception for malformed percent-encoded input (e.g., %ZZ, hello%world). The old querystring.unescape had a built-in fallback that returned the original string when decodeURIComponent threw, but the new code has no such protection. When called from src/routes/api.js:32, the thrown URIError is caught by the route handler's outer try/catch and results in a 500 Internal Server Error instead of the previous graceful handling that would have sanitized and returned results normally.

Suggested change
cleaned = decodeURIComponent(cleaned);
try {
cleaned = decodeURIComponent(cleaned);
} catch {
// If decodeURIComponent fails on malformed input (e.g., '%ZZ'), keep the original string
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant