Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: April 24, 2025
npm (Node Package Manager) is a popular dependency manager in the JavaScript ecosystem. The two commands commonly used for installing and resolving project dependencies are npm install and npm ci. Although similar, there are some notable differences in their purpose and operation.
In this tutorial, we’ll explore the differences between these two commands and how well they fit into development and automated workflows.
In a JavaScript or Node.js project, the npm install command installs the dependencies required to run the project. We define the project’s direct dependencies in the package.json file:
{
"name": "some-project",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"eslint": "^8.1.0"
}
}
In this example, we use a package.json with caret (^) version ranges to specify the versions of the lodash and eslint packages while allowing npm to accept new minor versions of these dependencies automatically.
When no lock file exists, npm install installs dependencies according to package.json and semantic versioning (SemVer). In addition, the command automatically generates a package-lock.json file, which locks a snapshot of installed versions.
In package-lock.json, we can see the resolved package versions installed within the specified ranges:
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"dependencies": {
...
}
Notice that the resolved version is the latest version that satisfies the SemVer range. This means that if we run npm install later, newer compatible versions will be installed as they become available.
The lock file acts as a snapshot of dependencies. It serves as a reference that npm install uses when possible to ensure consistency, without enforcing strict version locking.
npm install installs the versions listed in package-lock.json as long as they satisfy the version ranges defined in package.json. If the locked version is no longer compatible with the updated range, npm install resolves a new version accordingly and updates the lock file.
Let’s update the eslint package version to 9.0.0 in package.json:
"devDependencies": {
"eslint": "^9.0.0"
}
When running npm install, npm detects that the locked version (8.57.1) no longer satisfies the new version range (^9.0.0). Therefore, it resolves a compatible version from the registry, installs it, and updates package-lock.json accordingly.
"node_modules/eslint": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
...
}
However, if we use ^8.56.0, npm install will install version 8.57.1 from the lock file, as it satisfies the constraint.
Although the install command uses the lock file when it satisfies requirements, it doesn’t guarantee reproducible builds in all cases. If there’s a mismatch between package.json and package-lock.json, the latter will be updated with versions compatible with package.json.
Starting with version 5.7.0, npm introduced the ci (clean install) command to create reproducible builds. npm ci performs a clean read-only install from package-lock.json or npm-shrinkwrap.json:
Let’s take the example of a manual edit to the package.json file. We downgrade lodash to version 4.16.0, while package-lock.json still references version 4.17.21:
"dependencies": {
"lodash": "^4.16.0"
}
Running npm ci will create an error:
$ npm ci
npm ERR! code EUSAGE
npm ERR! `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 ERR! Invalid: lock file's [email protected] does not satisfy [email protected]
This error signals a drift between package.json and the lock file. If this is intentional, npm ci recommends updating the lock file via npm install.
Therefore, the npm ci command guarantees a clean, reproducible build that strictly conforms to the lock file.
Using npm ci is recommended in automated workflows, such as continuous integration and deployment pipelines. It’s particularly suitable for the first installation after a clone or pull in development environments.
The npm install and ci commands differ notably in terms of operation and usage.
Let’s take a closer look at the main differences between these two commands, as illustrated in the table below:
| npm install | npm ci | |
|---|---|---|
| Usage | Interactive development | Continuous Integration (CI) and clean installations |
| package-lock.json required | No | Yes |
| Handling mismatches | Updates package-lock.json if necessary | Fails if package.json and package-lock.json don’t match |
| Partial installation | Allows adding individual dependencies | Only installs the entire project |
| Handling node_modules | Keeps the existing folder | Deletes and recreates node_modules |
| lock file modifications | May modify package-lock.json | Never modifies package-lock.json |
| Dependencies check | Checks and updates as necessary | Skip dependency check |
These key differences highlight the core nuance between these two commands. We should use npm install in our development flows, where we’re iterating on our project. When we’re happy with our changes, we should use npm ci for build environments for consistent and repeatable builds for our production artifacts.
In this article, we’ve covered two essential npm commands for managing dependencies in a JavaScript project. We’ve looked at the behavior of both commands, highlighted what differs between them, and pointed out which things are similar.
Understanding the differences between npm install and npm ci ensures consistency and stability across projects, regardless of the environment.