1. Install an exact version of npm packages
If you got here because the npm's default semver range operator is causing you trouble and you're looking for a way out, I won't delay the answer. However, if you're also wondering why you should keep exact package versions in package.json, keep reading.
Answer: follow these steps for installing exact versions
Create an .npmrc file in the project root folder.
Add save-exact=true to the .npmrc file.
Profit! All future npm packages will be installed with exact versions.
Not recommended, but if you want to install only specific npm packages with an exact version, for some reason, you can specify it with save-exact configuration param of the install command, i.e. npm install --save-exact react. Alternatively with yarn: yarn add --exact react.
2. Semantic versioning (semver)
To better understand package versions in package.json and the default behavior of npm install, it's important to understand the versioning system used for npm packages — called semantic versioning (semver). The best explanation, especially for this article, can be found in Semver explained — why is there a caret (^) in my package.json?
3. Why to install an exact version
In summary, you get more certainty, predictability and more control over what package versions are installed.
Imagine a potential scenario: you use the npm's default semver range operator ("^”), thus you have "react": "**^16.1.0" in package.json. Then, there has been a new release of react: v16.2.0 . So when you run npm install, you will actually get v16.2.0, which is usually fine. However, in this case, the new react release contains a bug which will make your app crash. Because the package.json says "react": "^**16.1.0" instead of "react": "16.2.0", it's not easy to identify that the package update is the cause of the app crash. On the other hand, if you use the exact version, you can spot the bug when individually updating the react package.