Skip to Content
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes
Intermediate
0 likes
Courses
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes

package.json and Lock Files: Same Install Every Time

What package.json and package-lock.json record, what ^ means in a version, and when to use npm install or npm ci. After this, your project installs the same versions on every machine.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already know what ^ means in a version and why you commit package-lock.json, skip to "npm install vs npm ci".

"It works on my machine." Most of the time, when a JavaScript project works for one person and not another, the two machines installed different versions of the same packages. package.json and package-lock.json exist to stop that. Commit both. Use the lock file. That's most of this lesson.

package.json: what you asked for

Run npm init -y and then npm install dayjs in an empty folder, and package.json looks like this:

{
  "name": "order-report",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "type": "commonjs",
  "dependencies": {
    "dayjs": "^1.11.23"
  }
}

The parts that matter:

  • scripts: the commands for this project (npm test, npm run build).
  • type: how Node reads your .js files. commonjs means require(). module means import. Recent npm versions write "type": "commonjs" for you, which matters in the modules lesson.
  • dependencies: packages the code needs to run.
  • devDependencies: packages only needed while developing, like a test runner. Install them with npm install --save-dev vitest.

What the ^ means

"^1.11.23" doesn't mean "version 1.11.23". It means "1.11.23 or any newer 1.x version". So if the package publishes 1.12.0 next month, a fresh install takes 1.12.0.

  • ^1.11.23: newer minor and patch versions allowed (1.12.0 yes, 2.0.0 no).
  • ~1.11.23: only newer patch versions (1.11.24 yes, 1.12.0 no).
  • 1.11.23: exactly that version.

The idea is reasonable: minor versions shouldn't break anything. In practice, sometimes they do. That's why the lock file exists.

package-lock.json: what you actually got

The lock file records the exact version of every package installed, including the packages your packages depend on, plus where it came from and a checksum:

"node_modules/dayjs": {
  "version": "1.11.23",
  "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",
  "integrity": "sha512-QDTC...",
  "license": "MIT"
}

With the lock file, a teammate who installs next month gets 1.11.23, the same as you. Without it, they get whatever is newest that matches ^.

⚠ Warning: Commit package-lock.json. Don't delete it to "fix" an install error, and don't add it to .gitignore. Deleting it throws away the record of what worked and pulls the newest of everything at once. If something then breaks, you won't know which of the hundreds of updated packages did it.

npm install vs npm ci

  • npm install reads package.json, uses the lock file where it can, and updates the lock file if something changed. Use it when you add, remove or update a package.
  • npm ci installs exactly what's in the lock file. It deletes node_modules first, never changes the lock file, and stops with an error if package.json and the lock file disagree. Use it on a build server, in CI, and when you just cloned a project.

That error looks like this when someone added a package to package.json by hand and didn't update the lock file:

npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error
npm error Missing: ms@2.1.3 from lock file

That's a good error. It caught a mismatch before it shipped. The fix is to run npm install on your machine, check the changes to the lock file, and commit them.

A real example

Two developers start the same project a month apart. One gets a package at 3.4.0, the other at 3.5.0, which changed a default. Same code, different results, and the bug only shows up for one of them. If both had run npm ci from the same committed lock file, they would have the same 3.4.0, and the update to 3.5.0 would be a separate change someone made on purpose and could test.

💡 Tip: Before you update packages, run npm outdated. It lists each package with the version you have, the newest your ^ range allows, and the latest. Update a few at a time, run the tests, commit. Updating everything at once makes the next bug hard to trace.

Common mistakes

  • Not committing the lock file, or committing node_modules instead.
  • Deleting package-lock.json and node_modules as the first fix for every error.
  • Running npm install on the build server, so the lock file can change during the build.
  • Editing versions in package.json by hand and forgetting to run npm install.
  • Putting test tools in dependencies instead of devDependencies.

What happens if you skip this

Without a committed lock file, every machine installs a slightly different project. Most days nothing happens. Then one day a dependency of a dependency publishes a bad release, your server installs it during a deploy, and the site breaks with code nobody on your team changed.

Look at a project you have. Is package-lock.json committed, and does its build use npm ci or npm install?

About

What package.json and package-lock.json record, what ^ means in a version, and when to use npm install or npm ci. After this, your project installs the same versions on every machine.

Duration
5 min
Last update
09/03/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.