Skip to main content

semver vs compare-versions: Version Handling in Node.js (2026)

·PkgPulse Team

TL;DR

semver is npm's official semantic version library — the same one the npm CLI uses. It handles everything: version comparison, range checking (^1.0.0, >=2.0.0), coercion, and validity. compare-versions is the lightweight alternative — just 1KB, handles comparison and sorting with no overhead, perfect for simple a > b checks without the full SemVer range syntax. For anything touching npm, package managers, or version ranges: semver. For simple version comparison in browser or size-critical code: compare-versions.

Key Takeaways

  • semver: ~120M weekly downloads — npm's official library, full SemVer 2.0, ranges, coerce
  • compare-versions: ~15M weekly downloads — 1KB, just comparison, no ranges
  • Node.js has process.version — use semver to check if running Node meets requirements
  • semver.satisfies("18.3.0", ">=18.0.0") — how npm resolves peer dependency ranges
  • semver.coerce("v1.2.3-beta") — clean dirty version strings from user input or package metadata
  • Both are TypeScript-native in 2026

PackageWeekly DownloadsBundle SizeRange CheckingCoerceSorting
semver~120M~25KB
compare-versions~15M~1KB

semver

semver — npm's official SemVer implementation:

Basic version operations

import semver from "semver"

// Validate:
semver.valid("1.2.3")          // "1.2.3"
semver.valid("1.2.3-beta.1")   // "1.2.3-beta.1"
semver.valid("not-a-version")  // null

// Compare (returns -1, 0, or 1):
semver.compare("1.2.3", "1.2.4")  // -1 (1.2.3 < 1.2.4)
semver.compare("2.0.0", "1.9.9")  // 1  (2.0.0 > 1.9.9)
semver.compare("1.0.0", "1.0.0")  // 0  (equal)

// Boolean comparisons:
semver.gt("2.0.0", "1.9.9")   // true
semver.lt("1.0.0", "2.0.0")   // true
semver.gte("1.0.0", "1.0.0")  // true
semver.lte("1.0.0", "1.0.0")  // true
semver.eq("1.0.0", "1.0.0")   // true
semver.neq("1.0.0", "2.0.0")  // true

Range checking (npm-style)

import semver from "semver"

// Caret range (compatible with):
semver.satisfies("1.2.3", "^1.0.0")   // true  (>=1.0.0 <2.0.0)
semver.satisfies("2.0.0", "^1.0.0")   // false (breaks major)

// Tilde range (patch-level changes):
semver.satisfies("1.2.3", "~1.2.0")   // true  (>=1.2.0 <1.3.0)
semver.satisfies("1.3.0", "~1.2.0")   // false (breaks minor)

// Comparison operators:
semver.satisfies("2.0.0", ">=2.0.0")  // true
semver.satisfies("1.9.9", ">=2.0.0")  // false

// Hyphen range:
semver.satisfies("1.5.0", "1.0.0 - 2.0.0")  // true (inclusive range)

// Wildcard:
semver.satisfies("1.5.3", "1.5.x")  // true
semver.satisfies("1.6.0", "1.5.x")  // false

// OR ranges:
semver.satisfies("3.0.0", "^1.0.0 || ^2.0.0 || ^3.0.0")  // true

// Star (any version):
semver.satisfies("99.0.0", "*")  // true

Checking Node.js/runtime version requirements

import semver from "semver"

// Check if current Node.js meets requirements:
function checkNodeVersion(required: string) {
  const current = process.version  // "v22.13.0"

  if (!semver.satisfies(process.version, required)) {
    throw new Error(
      `Requires Node.js ${required}, but found ${current}. Please upgrade.`
    )
  }
}

checkNodeVersion(">=18.0.0")  // OK on Node.js 22
checkNodeVersion(">=22.0.0")  // Throw on Node.js 18

// Check engine requirements from package.json:
async function validateEngines() {
  const pkg = JSON.parse(await fs.readFile("package.json", "utf8"))
  const engines = pkg.engines

  if (engines?.node && !semver.satisfies(process.version, engines.node)) {
    console.warn(
      `Warning: This package requires Node.js ${engines.node}. ` +
      `You have ${process.version}.`
    )
  }
}

Coerce — clean dirty version strings

import semver from "semver"

// Handle user-provided or registry versions that aren't clean:
semver.coerce("v1.2.3")          // SemVer { major: 1, minor: 2, patch: 3 }
semver.coerce("v1.2.3-beta")     // { major: 1, minor: 2, patch: 3 } (pre-release stripped)
semver.coerce("1.2")             // { major: 1, minor: 2, patch: 0 } (fills patch)
semver.coerce("3")               // { major: 3, minor: 0, patch: 0 } (fills minor + patch)
semver.coerce("  v1.2.3  ")      // Trims whitespace
semver.coerce("not a version")   // null

// Use for package metadata that may have non-standard version strings:
function parsePackageVersion(raw: string): string | null {
  const coerced = semver.coerce(raw)
  return coerced ? coerced.version : null
}

Increment versions

import semver from "semver"

const current = "1.2.3"

semver.inc(current, "patch")   // "1.2.4"
semver.inc(current, "minor")   // "1.3.0"
semver.inc(current, "major")   // "2.0.0"

// Pre-release versions:
semver.inc(current, "prerelease", "beta")  // "1.2.4-beta.0"
semver.inc("1.2.4-beta.0", "prerelease")   // "1.2.4-beta.1"
semver.inc("1.2.4-beta.1", "patch")        // "1.2.4" (promotes to stable)

Sorting versions

import semver from "semver"

const versions = ["1.10.0", "1.9.0", "2.0.0", "1.1.0", "0.5.0"]

// Sort ascending:
const sorted = versions.sort(semver.compare)
// ["0.5.0", "1.1.0", "1.9.0", "1.10.0", "2.0.0"]
// Note: 1.10 > 1.9 (numeric, not lexicographic — semver handles this correctly)

// Sort descending:
const sortedDesc = versions.sort(semver.rcompare)
// ["2.0.0", "1.10.0", "1.9.0", "1.1.0", "0.5.0"]

// Max/min:
semver.maxSatisfying(["1.0.0", "1.5.0", "2.0.0"], "^1.0.0")  // "1.5.0"
semver.minSatisfying(["1.0.0", "1.5.0", "2.0.0"], "^1.0.0")  // "1.0.0"

Range analysis

import semver from "semver"

const range = new semver.Range("^1.0.0")

// Check range properties:
range.test("1.5.0")   // true
range.test("2.0.0")   // false

// Intersect ranges:
const a = new semver.Range(">=1.0.0")
const b = new semver.Range("<2.0.0")
semver.intersects(a, b)  // true (ranges overlap)

// Outside check:
semver.outside("3.0.0", "^1.0.0", ">")  // true (3.0.0 is > the max of ^1.0.0)

compare-versions

compare-versions — 1KB version comparison:

Basic usage

import { compareVersions, compare, satisfies, validate } from "compare-versions"

// Compare:
compareVersions("1.2.3", "1.2.4")   // -1
compareVersions("2.0.0", "1.9.9")   // 1
compareVersions("1.0.0", "1.0.0")   // 0

// Boolean helpers:
compare("1.0.0", "2.0.0", "<")   // true
compare("2.0.0", "1.0.0", ">")   // true
compare("1.0.0", "1.0.0", ">=")  // true
compare("1.0.0", "1.0.0", "<=")  // true
compare("1.0.0", "1.0.0", "=")   // true
compare("1.0.0", "2.0.0", "!=")  // true

// Validate:
validate("1.2.3")          // true
validate("not-a-version")  // false

// Range satisfies (simple operators only — no ^ or ~ ranges):
satisfies("1.2.3", ">=1.0.0")   // true
satisfies("1.2.3", ">=1.0.0 <2.0.0")  // true (multiple conditions)
satisfies("2.0.0", ">=1.0.0 <2.0.0")  // false
// Note: No caret (^) or tilde (~) support — use semver for those

Sorting

import { compareVersions } from "compare-versions"

const versions = ["1.10.0", "1.9.0", "2.0.0", "1.1.0"]

const sorted = versions.sort(compareVersions)
// ["1.1.0", "1.9.0", "1.10.0", "2.0.0"]
// Handles numeric comparison correctly (1.10 > 1.9)

When to use compare-versions

// compare-versions excels for:
// 1. Browser environments where bundle size matters
// 2. Simple comparison/sorting without npm-style ranges
// 3. Zero-dependency scripts

// Example: version badge in a browser widget
import { compare } from "compare-versions"

function isOutdated(current: string, latest: string) {
  return compare(current, latest, "<")
}

// Example: sort user-inputted version list
import { compareVersions } from "compare-versions"

const userVersions = userInput.split(",").sort(compareVersions)

Feature Comparison

Featuresemvercompare-versions
Bundle size~25KB~1KB
npm ranges (^ ~)
Simple operators (> < =)
Coerce dirty versions
Increment (major/minor/patch)
Max/min satisfying
TypeScript
Browser-safe
Pre-release handling✅ Full✅ Basic

When to Use Each

Choose semver if:

  • Working with npm packages or package.json version ranges
  • You need satisfies() with caret (^) or tilde (~) ranges
  • Building package managers, version resolvers, or update checkers
  • You need coerce() to clean up dirty version strings
  • Any serious version manipulation work

Choose compare-versions if:

  • Bundle size is critical (1KB vs 25KB)
  • Browser environment where you just need a > b or a === b
  • Simple sorting of version strings without complex range logic
  • You don't need ^1.0.0 style ranges — just >=1.0.0 operators

Use Node.js built-ins if:

  • Just comparing process.version to a fixed string — parseInt on the major is often enough
  • Simple scripts without complex version logic

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on semver v7.x and compare-versions v6.x.

Compare utility and version management packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.