My Preferred Node.js Workflow

- 17 July 2025 - 7 mins read

As an SRE, I’ve learned the hard way that dependencies rot faster than you think. If you’ve ever opened a Node.js project after a year of neglect, you know the feeling: dozens of outdated packages, a handful of breaking changes, and a mountain of tech debt waiting to make you bankrupt.

Most developers defer upgrades until they’re unavoidable, as it’s a bit of a manual task. But delaying only multiplies the pain.

My approach is the opposite: I always face breaking changes as soon as they happen.

This mindset has saved me countless hours of firefighting and given me confidence that my projects are always maintainable, deployable, and ready for the latest runtime versions. Let me explain why.

Why I Don’t Fear Breaking Changes

When you skip updates for months, you don’t just accumulate drift on version numbers, you accumulate a whole ecosystem drift:

  • Old dependencies may rely on deprecated APIs
  • New security patches pile up
  • Major versions bring new behavior you’re not mentally prepared for

Then one day, you’re forced to upgrade and… you’re hit with a tsunami of breaking changes all at once.

Instead, I prefer continuous exposure to change:

  • Updating frequently means you only face small, incremental breaking changes
  • The diff is always manageable
  • You develop a muscle memory for upgrades, making future updates trivial

It’s like paying off tech debt in micro-installments instead of waiting for bankruptcy.

Saving Exact Versions

Another important habit: I always pin exact versions in the package.json file.

Why? Visibility and reproducibility.

When you have "^1.2.3", what’s actually installed can vary over time:

  • Today it might resolve to 1.2.3
  • Tomorrow it could silently install 1.9.9

This makes debugging “works on my machine” issues harder.

By pinning exact versions:

  • I know exactly what’s running in production
  • I avoid silent minor updates that break things unexpectedly
  • I keep diffs clear in pull requests

Continuous Updates vs. Letting Dependencies Rot

Here’s the real tradeoff between continuous updates and deferring them:

Continuous Updates Rotting Dependencies
✅ Small, incremental breaking changes ❌ Massive upgrade pain all at once
✅ Always aligned with latest ecosystem ❌ Risk of depending on deprecated APIs
✅ Easier to adopt new Node.js LTS ❌ Locked into old runtime/tooling
✅ Clear visibility on upgrades ❌ Confusing diff with hundreds of changes
✅ Builds confidence & maintainability ❌ Builds fear & tech debt over time

My Preferred Workflow

Every time I open a project, I refresh its entire dependency tree to the latest ecosystem state:

  1. Wipe the slate clean (rm -rf node_modules package-lock.json)
  2. Run npm outdated and update package.json to the latest versions, even major versions
  3. Install with pinned exact versions
  4. Reinstall cleanly to verify consistency (rm -rf node_modules package-lock.json && npm install)
  5. Commit the changes before writing any new code

I do this every single time I open a project, and I’ve been doing this for years. Even daily.

This ensures I’m never coding on top of stale tech. It also means that when a PR comes in, I don’t just review code, I also look at dependency upgrades. Keeping dependencies fresh is part of the code review culture.

Why This Improves Maintainability

  • Less tech debt: You’re always one step away from a “clean” upgrade
  • Confidence to move fast: Latest Node.js LTS? No problem, everything is already up-to-date
  • Easier reviews: Breaking changes are small and visible, not buried in years of drift
  • Predictable builds: What you see in package.json is exactly what IS installed

Ultimately, this workflow helps you keep projects healthy, resilient, and ready for the future.

The Script I Use

To make this painless, I use a small Bash script. Drop this at the root of your project and run it before you start any work:

#!/usr/bin/env bash
set -euo pipefail

echo "🚀 Updating dependencies to ABSOLUTE latest and pinning exact versions..."

# 1️⃣ Clean everything
echo "🧹 Removing node_modules and package-lock.json..."
rm -rf node_modules package-lock.json

# 2️⃣ Update package.json to latest versions (major bumps included)
echo "⬆️  Updating package.json with latest versions (major bumps included)..."
npx npm-check-updates -u

# 3️⃣ Install & pin exact versions (no ^ or ~)
echo "📦 Installing and pinning exact versions..."
npm install --save-exact

# 4️⃣ Double clean to ensure lock consistency
echo "🧹 Re-cleaning and reinstalling to verify lockfile..."
rm -rf node_modules package-lock.json
npm install

# 5️⃣ Show outdated (should be empty now)
echo "✅ Checking for outdated deps..."
npm outdated || echo "🎉 All dependencies are fully up-to-date!"

echo "✅ Done! You’re now on the absolute latest versions."

Usage:

chmod +x update-latest.sh
./update-latest.sh

Now your project will always run on the freshest dependencies, with exact versioning, and you’ll build a natural rhythm for handling upgrades before they snowball.

Treating dependency updates as a first-class task, not an afterthought, keeps your projects lean and maintainable. It also forces you to stay aligned with the evolving Node.js ecosystem—meaning you’re never locked into an ancient runtime or tooling chain.

Most importantly, you’re maintaining confidence on the platform. And confidence is what keeps SREs and developers sleeping well at night.


Share: Link copied to clipboard

Tags:

Previous: Código y Caos - De RSA a la era cuántica: la respuesta de QuReady
Next: El palet, el arte y la risa de los ignorantes

Where: Home > Technical > My Preferred Node.js Workflow