Learn the best approach to install npm dependencies in your project

Felipe Plets profile photo
By Felipe Plets (He/Him) • 

Cat representing npm ci with the word consistent vs cat wearing sunglasses representing npm install with the word dynamic

I've been using npm for around 10 years now and got to learn a lot about it's behaviours and features and still get a little surprised how often I meet developers that don't know when to use npm ci and what are the differences between running npm ci and npm install so let's have a deep dive into both commands and try to better understand when to use one or the other.

First and foremost both commands are used to install dependencies that are described in the package.json file in a project using npm. So virtually you can use any of both and get similar results, but as I said you virtually get same results.

npm install

Aliases: npm i and npm add

Meaning: This is pretty straightforward, install means something will be installed.

How it works:

  • it will install a specific package if a package name is set as a parameter (i.e.: npm i typescript will install typescript) or all packages described in the package.json file in the first folder it find traversing directories from the current one upwards.
  • it will resolve the dependency versions based on the version definition that can use leading characters ^ or ~ that will allow versions with newer minor or patch versions considered "safe" for the project, usually based on the knowledge of the team about the release strategy of the dependency. (i.e.: ^1.0.0 allows 1.x.x versions to be installed while ~1.0.0 allow 1.0.x versions to be installed, if no character is preceding the version only the strict same version will be installed.)
  • it will update the package-lock.json file with the latest definitions that meet the requirements above.

npm ci

Aliases: none

Meaning: CI means Cleans Install (although I've seen developers saying it is "Continuous Integration") this is usually a misunderstanding based on the fact that both terms are used in npm documentation, and the CI meaning is not clarified there.

How it works:

  • it will check if there is a node_modules folder and will delete it
  • it will raise an error if package-lock.json does not match package.json
  • it will not resolve dependencies versions as it will actually install the exact versions defined in package-lock.json (or npm-shrinkwrap.json)

Comparison

There are three use cases for using those commands and I want to list them and explore a little bit each of them to understand which and when to use each of the two commands.

1. Install a new package

To install a new package you can only do it by using npm install package_name or one of the aliases (npm i / npm add) they will do exactly the same thing. This is a bit confusing because yarn install and yarn add are different things, but this is subject for another post.

2. Update dependencies

As much as possible you should update your dependencies and make sure you have an update strategy in place either to have known dependencies bugs fixed but also to get improvements like performance, developer ergonomics, standards and new features. As we saw in the npm install section of this article it will update using the version definitions using the leading characters ^ (caret) or ~ (tilde). So in case you are looking to update dependencies your first choice should be npm install to get the latest versions defined as "safe" in your package.json (as also npm update to update to latest versions that are out of the so defined "safe" versions).

3. Install dependencies

In case you are looking for installing dependencies to run the app in a fast and consistent way you are looking for npm ci. It is faster than npm install (around 75% faster in a couple of tests I've run) and consistent because it will install the exact same versions defined in package-lock.json. Definitely this is a must have for server environments, but also on developer environments in order to avoid issues because of inconsistent dependency installation. In addition, if you use npm install in the server and get inconsistent behaviors it may be difficult to replicate locally as your only safe option would be to download the package-lock.json from the server as it will be updated each time you run the npm install, if you try it locally the versions may have changed in the meantime or even be cached in another server which would raise an error when installing with npm ci as the exact version would not be available on the server. (I've dealt in the past with projects using a Nexus Server as proxy and local environments were cached daily while the server was reaching directly the npm servers and getting a newer version of the library.)

Conclusion

Scenario npm ci npm install
Install a new package 🚫
Update dependencies To the latest compatible version according to version definitions. 🚫
Install dependencies Installation to run the app based on the existing dependency tree ⚠️ *

* you can use install in this case but usually you shouldn't

This post was first featured at Felipe Plets blog

Share: