beginner17 min

CommonJS vs ES Modules, npm, and Dependency Management

Node's two module systems, why they don't mix carelessly, and what npm actually manages beyond just downloading packages.

What you'll learn

  • Distinguish CommonJS (require/module.exports) from ES modules (import/export) in Node
  • Explain what package.json and package-lock.json each guarantee
  • Identify the difference between a dependency and a devDependency

Prerequisites

Explanation

Node supports two module systems, and mixing them carelessly is a common source of confusing errors for anyone coming from a browser-only JavaScript background. CommonJS (require("./thing"), module.exports = ...) is Node's original, synchronous module system — a require call resolves and loads a module immediately, blocking until it's ready, which works because it's reading from the local filesystem, not the network. ES modules (import thing from "./thing.js", export default ...) are the same import/export syntax used in browser JavaScript and modern bundlers, and Node supports them too — but a project has to declare which system it's using, either via "type": "module" in package.json (opts the whole project into ES modules) or by using the .mjs/.cjs file extensions explicitly. A file written with import syntax in a project still configured for CommonJS throws a syntax error — this is a configuration mismatch, not a bug in the import statement itself.

package.json is a project's manifest: its name, version, scripts, and — critically — its dependencies, declared with a version range (like ^4.18.0, meaning "4.18.0 or a later compatible minor/patch version"). package-lock.json records the exact, specific version of every dependency (and every dependency of every dependency) that was actually installed, so a fresh npm install on a different machine, or a different day, reproduces the identical dependency tree — not just something "compatible enough." Committing package-lock.json to version control is what makes a team's (or a CI server's) installs reproducible; relying on version ranges alone means "works on my machine" can genuinely mean something different from "works on yours," installed on a different day.

Dependencies versus devDependencies is a distinction about when code is needed: a dependency (express, a database driver) is required for the application to actually run in production. A devDependency (a test runner, a linter, a local dev-only tool) is needed only while developing — npm install --production (or an equivalent in a deployment pipeline) skips devDependencies entirely, since a production server never runs your test suite or linter as part of serving requests.

Example

The two module syntaxes side by side -- functionally equivalent, but requiring different project configuration to use correctly.

// CommonJS (the historical Node default)
// const express = require("express");
// module.exports = { start };

// ES modules (requires "type": "module" in package.json, or a .mjs file)
// import express from "express";
// export { start };

function describeModuleSystem(usesTypeModule) {
  return usesTypeModule ? "ES modules (import/export)" : "CommonJS (require/module.exports)";
}

console.log(describeModuleSystem(true));
console.log(describeModuleSystem(false));

Try it yourself

Change the argument and re-run to see the module system description flip.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write satisfiesRange(version, rangePrefix) that returns true if `version` (a string like '4.18.2') starts with the same major version number as `rangePrefix` (a string like '^4.18.0' -- extract the leading number after the caret). This is a simplified model of what npm's caret-range matching checks.

Checks: correctly matches a compatible version · correctly rejects an incompatible major version

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write categorizeDependency(name, isNeededInProduction) that returns 'dependency' if isNeededInProduction is true, 'devDependency' otherwise. Then write listProductionDependencies(packages) where packages is an array of { name, isNeededInProduction }, returning just the names of the ones needed in production.

Checks: correctly categorizes a production dependency · correctly categorizes a devDependency · correctly filters to only production dependencies

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Using `import` syntax in a project not configured for ES modules (no "type": "module" and no .mjs extension), producing a confusing syntax error.
  • Not committing package-lock.json, so different machines or CI runs can install subtly different dependency versions within the same declared range.
  • Installing a tool only needed for development (a linter, a test runner) as a regular dependency instead of a devDependency, bloating what a production install pulls in.

Knowledge check

Knowledge check

1. Why does an `import` statement fail in a Node project that isn't configured for ES modules?
2. What does package-lock.json guarantee that package.json's version ranges alone do not?
3. What distinguishes a devDependency from a regular dependency?

Takeaway

Node supports both CommonJS and ES modules, but a project must be configured for one consistently; package-lock.json (not just package.json's version ranges) is what makes installs truly reproducible across machines.

Summary

This lesson covered the two Node module systems and why mismatched configuration causes errors, plus what package.json and package-lock.json each guarantee and the practical difference between dependencies and devDependencies.

References

Your notes

Notes save automatically.