Skip to main content

Why you should use package-lock.json

A guide to using package-lock.json in NPM
In this article, we’ll look at package-lock.json, why it’s important, and how it’s best used along with NPM CLI in your day-to-day life.

History

NPM version 5 introduced package-lock.json as a mechanism to capture the exact dependency tree installed at any point in time.
This helps with collaboration across different environments in which you want everyone fetching dependencies for a specific version of your project to fetch the same tree.
package.json defines the required dependencies and their respective versions using semantic versioning. However, semantic versioning can be tricky.
Consider a dependency stated as "express": "^4.16.4".
The publisher of this module (without using package-lock.json) would have express version 4.16.4 installed since they installed the latest version.
If express has published a new version by the time I download this module and try to install dependencies on it, I can download the latest version.
The caret symbol tells us exactly that.
The problem with the above is that if version 4.17.x contains a bug, my local setup will fail, but the publisher’s will continue to work fine on the previous version.
The same thing could happen in the production environment, and you’d have no idea why it was failing.
Prior to NPM version 5, you would use shrinkwrap. It differs from package-lock.json because it’s allowed to be published with your module on the NPM registry, whereas package-lock.json is not.
If all members can use NPM+5, it’s best to go with package-lock.json for unpublished projects.
But if you are developing a module and you intend to publish it, you might need to think about whether you want the clients to install the exact dependency tree you dictate, or if you want to be more flexible about it. Here’s a more detailed version on the subject.
So, package-lock.json will describe the exact dependency tree currently installed. The format is described in the NPM documentation page.
By committing it to your VCS — which you should absolutely do — you can go back in history and replicate the exact dependency tree from that time.
Make sure to always commit package-lock.json to your VCS to keep track of exact dependency trees at any given time.
It will ensure that all clients that download your project and attempt to install dependencies will get the exact same dependency tree. Furthermore, it’ll ensure you’re able to check out previous commits and replicate the dependencies state of each commit.

package.json vs package-lock.json

Make sure you don’t change package-lock.json directly. That’s being handled automatically by NPM. It reflects changes made to package.json to package-lock.json and keeps it up to date.
However, this only happens if you use NPMs’ CLI to make changes. If you manually change package.json, don’t expect package-lock.json to update. Always use the CLI commands, like installuninstall, etc.

How to use the NPM CLI

NPM will auto-generate a package-lock.json when you first use it in a fresh project.
Then, you can use NPM as normal.

npm install (with specific modules as arguments)

install can be used with the names of modules to install as arguments, which will alter both package.json and package-lock.json since the dependency tree will change.
Consider the following example:
npm install express body-parser cors

npm install (without arguments)

install will attempt to install all dependencies in respect to package-lock.json.
A key point here is that install can alter package-lock.json if it registers that it’s outdated.
For example, if someone manually alters package.json — say, for example, they remove a package since it’s just a matter of removing a single line — the next time that someone runs npm install, it will alter package-lock.json to reflect the removal of the previous package.
That can be tricky. Imagine pulling the latest version of your project, running npm install to get up to date, only to find that you immediately have a bunch of changes in your tree that make no sense.
It’s also highly likely that the changes in your tree would make no sense to the people reviewing your changes.

npm uninstall

Similar to install but with names of modules to remove as arguments. This will alter both package.json and package-lock.json.

npm update

update will read package.json to find any dependencies that can be updated. Subsequently, it will construct a new dependency tree and update the package-lock.json as well.
Remember semantic versioning? Say we have a dependency in our package.json stated as ^1.4.5.
The ^ character tells NPM to check if there’s a newer version under the 1.X.X scope and if there is, to install that. Similarly, the ~ character will only go up to hot-fixes, or 1.4.X.
You could also omit the special character and keep a fixed version, which makes package-lock.json less helpful (but not useless).

npm ci

ci will install all dependencies in respect to package-lock.jsonsimilar to install. The key difference here is that it won’t alter package-lock.json under any circumstances.
Its purpose is to be used by environments, e.g. build servers, where installation happens in an automated way.

Conclusion

Remember these key takeaways when using package-lock.json:
Don’t use npm install without arguments to fetch dependencies — use npm ci for that. You can use the npm install to install specific dependencies.
Use npm ci everywhere when you only want the local dependencies tree — even on your local development environment.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...