I had some confusions about package.json and package-lock.json and what is used when but now that I have been “enlightened” I’ll record my new knowledge in this article.

package.json and package-lock.json

package.json lists, among other things, the dependencies you need for your JavaScript project (if you use npm). You’d edit this file manually. In contrast package-lock.json is generated by npm.

Example package.json:


Often, the versions listed in package.json are given as ranges. npm uses SemVer, meaning a version scheme in three parts like a.b.c where a,b and c are numbers (also called “major.minor.patch”). “~a.b.c” means a and b are fixed and the last part can be c or any greater number: “~4.17.1” means “4.17.x for x>=1”. “^a.b.c” means a is fixed and minor and patch version is variable: “^4.17.20” means “4.x.y for either (x=17 and y>=20) or x>18”.

In contrast, package-lock.json contains the exact versions of the project’s dependencies (and their transitive dependencies and so on). When package-lock.json is generated or updated, the version range in package.json is resolved to the latest “allowed” version.

Generating and updating package-lock.json

How do you create the lock file? “npm install” will do it.

How do you update the lock file? “npm update” will do it, usually. Say package.json states module A in version “^3.4.5” and the existing state module A in version “3.4.20“. Then you run “npm update A” or “npm update” and when there is a version “3.5.2” of A out there, npm will update the lockfile to version “3.5.2” of module A.

If package.json and the lock file are out of sync (the version in package-lock.json is out of the range specified in package.json), “npm install” will correct the package-lock.json file.

Why committing the lock-file?

The general advice is to commit package-lock.json to your repository. That way, every developer will be using the same versions: the ones listed in the lock-file (using “npm install“).

How to upgrade dependencies?

npm outdated” shows outdated dependencies and “npm update <pkg> --save” updates a package.  Commit both files.

Another way is to use tools like dependabot or renovate which check for new versions. If a new version of a module is detected, these tools will create a branch using the new version. CI pipelines are run and pull/merge requests are created or even merged automatically.

CI pipelines

There is a special command for CI pipelines: “npm ci“. It will fail if the lock file is missing or is out of sync with package.json. So the build will fail if “npm install” would change the lock-file.

npm ci” ensures that your build is always based on a consistent set of dependencies, which is important for reproducibility and stability. It also helps avoid problems that can arise from using different versions of the same package across different stages of the pipeline.

Pinning dependencies

Pinning a dependency means using an exact version in package.json.