Skip to content

What Is package-lock.json?

A tutorial explaining the difference between package-lock.json and package.json, and how package-lock.json can help avoid installing modules from the same package.json that result in two different installs.

You may have noticed it before; you install a package using npm and suddenly a new file called package-lock.json appears in your project directory. Don’t delete that package-lock file, run npm install and regenerate it! package-lock.json, a file generated by npm since v5 was released in 2017, does what its name suggests: helps lock package dependencies down, as well as their sub-dependencies. A fairly new addition to npm, it’s something you should be using in your own projects today.

In this tutorial we’ll:

  • Learn about package-lock.json files
  • Review why package-lock was added to npm
  • Outline the reasons why lockfiles should be used by your application

Goal

Understand how package-lock affects the behavior of npm install, and why we want to use it.

Prerequisites

Watch: What is package-lock.json?

How lockfiles manage the dependency tree

Our package.json file tracks only top-level dependencies and their associated versions. But each of those dependencies can have their own internal dependencies, and each of those can have their own dependencies, and etc., forever. This relationship between all the dependencies and sub-dependencies in our project is called the “dependency tree” of our project, and represents every module our project depends on and what version is required.

Installing a dependency with npm actually fetches all the underlying dependencies that package needs, and installs them to our node_modules/ folder as well. This process is abstracted away from us, and that convenience can introduce problems.

The package-lock file is a snapshot of our entire dependency tree and all the information npm needs to recreate the state of our node_modules/ folder. When package-lock is present alongside our package.json, npm install will respect the package-lock file and install the exact versions of all dependencies and sub-dependencies specified by package-lock.

That’s a lot of information. Let’s think through a practical example of this.

Use a lockfile to prevent dependency drift

Let’s say we install a package to our project — we’ll call it foo-lib. We run npm install foo-lib, our package.json is updated, the package is installed in our node_modules/ folder, and everything works fine.

But let’s say that foo-lib has a dependency of its own, called bar-lib. At some point an update was pushed to bar-lib, which completely breaks the package, or somehow introduces a bug that wasn’t there before. The next time you checkout a fresh copy of your project and run npm install to fetch all the project’s dependencies, foo-lib will be installed like before, but it will include a broken dependency, unbeknownst to you. When you go to run your project, suddenly things are broken, which is perplexing, because from your perspective you didn’t change any dependencies. The package.json is exactly the same as when you last installed the project’s dependencies on another machine. What gives?

In the example above, your package.json never changed, but the state of your node_modules/ folder changed between installs of your application. A sub-dependency drifted underneath you, without you ever realizing it. This sort of issue is actually a symptom of how npm resolves version numbers. You can learn more about that in What Is Semantic Versioning (SemVer)?

Enter the package-lock file. In the example, if you’re project had a package-lock file present, then you would never have had an issue with dependencies changing underneath you. The package-lock specifies exactly the state of your dependency tree to reproduce when installing your project dependencies. It will make sure that you get the exact same version of each dependency and sub-dependency, every time.

In order to be useful, package-lock.json needs to be committed to version control. That way it is distributed with your project, and any developer can make sure they are using exactly the same dependency tree as everyone else, every time.

How lockfiles are created and managed

Below is just a part of a package-lock.json file:

{
"name": "my-project-name",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
"requires": {
"mime-types": "~2.1.18",
"negotiator": "0.6.1"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.18.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
"requires": {
"bytes": "3.0.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "~1.6.3",
"iconv-lite": "0.4.23",
"on-finished": "~2.3.0",
"qs": "6.5.2",
"raw-body": "2.3.3",
"type-is": "~1.6.16"
}
}
}
}

Package-lock is not meant to be human-readable, and it’s not meant to be edited manually. It is something the npm CLI generates and manages for us.

Since npm v5 released in 2017, a package-lock.json file is generated or updated automatically whenever we run npm install or make changes to our list of dependencies in package.json.

A package-lock file is not something you create or edit yourself, it is meant to only be changed by the npm cli as a side effect of altering your dependency tree. Whenever we run an npm command that updates or alters our dependencies, like npm install <package> or npm uninstall <package> or npm update, our package-lock will be updated to reflect the new state of our dependency tree. Again, make sure you commit this file to version control whenever it changes.

Track package-lock.json in version control

The benefit of checking in your package-lock to git is tracking the state of your node_modules/ folder without having to commit the folder itself to version control. Our node_modules/ folder is never intended to be checked in to version control—it’s simply too big and we already track dependencies with package.json. Adding package-lock.json into our version control system allows us to see exactly what the state of our node_modules/ folder was at any given commit. This is really useful if you ever need to look back through your dependencies to see where exactly a bug was introduced.

How package-lock differs from npm-shrinkwrap

Locking dependencies is not new in the Node.js world, or in the programming world in general. (The PHP package manager Composer, for example, uses lockfiles.) The new package-lock file behaves almost exactly the same as a file npm already uses called npm-shrinkwrap.json, which used to be how dependencies were locked down.

They differ in one main way: package-lock.json is ignored by npm when publishing to the NPM registry. When publishing to the NPM registry, package-lock is ignored. If you want to lock dependencies in a published package, that’s what npm-shrinkwrap is for. The intention is to only have one of these files in the root of your directory, and if you have both, npm-shrinkwrap takes precedent, and package-lock is ignored.

To create an npm-shrinkwrap file, run npm shrinkwrap. Under the hood, this command just renames your package-lock file to npm-shrinkwrap. Functionally, these files are exactly the same. They contain the same information, and function in the same way. The only difference is npm-shrinkwrap is meant to be used in packages which are published to the registry, and package-lock is for use in project’s not published to the registry. You’re not required to use npm-shrinkwrap, but if you have a reason that you need to lock dependencies in a publicly-available project, using npm-shrinkwrap is how you do it.

Recap

A lockfile is a snapshot of the entire dependency tree, including all packages and all their dependencies, and all their resolved version numbers. It’s a safeguard against your project’s dependencies drifting between installs, and is updated automatically when using npm to update, add, or remove dependencies. Committing your package-lock file to version control will help ensure that whoever runs your project will end up with the same dependency tree that you had when developing it. You learned how package-lock affects the behavior of npm install and how it solves the problem of dependency drift between installs.

Further your understanding

  • Open the package-lock file for a project. What information do the fields describe?
  • How does yarn behave if a package-lock file is present?

Additional resources