<m>If you haven't already, check out the [previous post](https://www.coana.tech/post/understanding-semantic-versioning-a-guide-for-npm-developers-part-1) where I cover the basic principles of semantic versioning. In this post, we'll take a deeper look at how to handle dependency updates in a system like npm that follows SemVer.</m>
<m>One of the appealing aspects of SemVer is that, in an ideal scenario, minor and patch updates are always compatible with the previous version. It is therefore tempting to think that it's best to always apply minor and patch updates as soon as they're released and thereby get security patches immediately after they become available.</m>
<m>**If updates are backward compatible, why not just apply them automatically immediately?**</m>
<m>In fact, that is what the `^` [version constraint](https://docs.npmjs.com/about-semantic-versioning#using-semantic-versioning-to-specify-update-types-your-package-can-accept), which is used by default, is designed to do. For example, if you have `pkg: ^1.2.3` in your package.json file's dependency section, it tells npm to select the latest version of `pkg` that falls within the major range of `1`. So if the latest version of `pkg` is `2.4.0`, but the latest version in major range `1` is `1.8.2`, then `npm install` will install version `1.8.2` of `pkg`. However, what developers have learned through countless examples (see for example [this GitHub issue](https://github.com/debug-js/debug/issues/347)) is that determining if code changes are backward compatible is extremely difficult. This has led to a broad mistrust in semantic versioning. Some packages, most notably [TypeScript](https://github.com/microsoft/TypeScript/issues/14116#issuecomment-280410804) don't even attempt to follow semantic versioning. </m>
<m>Due to the difficulty of trusting semantic versioning in practice, npm now uses a [package-lock.json](https://docs.npmjs.com/cli/v6/configuring-npm/package-locks) file (yarn uses [yarn.lock](https://classic.yarnpkg.com/lang/en/docs/yarn-lock/) and pnpm uses pnpm-lock.yaml). When you run `npm install`, it records the exact versions of all dependencies (direct and indirect) in the lock file. Subsequent runs of `npm install` use the versions specified in the lock file rather than the package.json file. In other words, the lock file *locks* the dependency versions to fixed versions, and no updates, whether major, minor, or patch, will be applied automatically as long as the lock file persists. If you'd like to learn more about working with lock files, check out our blog post on the topic [here](https://www.coana.tech/post/navigating-lock-files-best-practices-and-tips).</m>
<m>![What the hell is a package-lock.json meme](https://raw.githubusercontent.com/coana-tech/assets/main/blogs/package-lock-meme.png)</m>
<m>Since a lock file locks all versions, updates must be applied manually, which means that you should check for updates at regular intervals. You can use the `npm outdated` command to check for outdated dependencies (learn more about `npm outdated` in [this post](https://www.coana.tech/post/hidden-gems-in-npm)). Since package developers generally still attempt to follow semantic versioning, I recommend that you apply minor and patch updates quite frequently. Good test coverage should ensure that everything still works.</m>
<m>If you primarily care about security updates, then the [npm audit](https://docs.npmjs.com/cli/v9/commands/npm-audit) tool can be used to warn about security patches as soon as they're released. Since `npm audit` is only run as a sub-step of `npm install` or manually through the `npm audit` command, I recommend also using a vulnerability disclosure bot such as [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates), [Renovate](https://docs.renovatebot.com/getting-started/installing-onboarding/), or [Snyk Open Source](https://snyk.io/product/open-source-security-management/). These bots will notify you, typically through a pull request, as soon as a vulnerability becomes public.</m>
<m>With the use of lock files, one might wonder if it's still necessary to set range constraints (`^`, `~`, etc.). While the role of version constraints may be reduced, there are still a few reasons to set them:</m>
<m>With the use of lock files, one might wonder if it's still necessary to set version range constraints (`^`, `~`, etc.).
While the role of range constraints may be reduced, it is still beneficial to set them.
Primarily, because some tools, like `npm outdated`, will display updates that fall within the accepted constraints differently.
Remember that even if you use fixed version numbers in the package.json file, only the direct dependency versions are fixed.
The dependencies of your dependencies may still change! This is why it is very important to store your lock file in your source control system such as git.</m>
<m>Some teams still don't commit lock files. In my opinion, this is a grave mistake since one of the main purposes of lock files is to give you reproducible installations across computers. However, it may not be possible to change this culture overnight, so in this particular case, you may be better off using fixed versions in the package.json file.</m>
<m>To sum up, semantic versioning provides a framework for application developers to automatically apply many updates without worrying about breaking changes. However, it has proven difficult to follow in practice, which has led to the introduction of lock files. Lock files ensure reproducible installations at the cost of having to update packages manually. Since most package developers still attempt to follow semantic versioning, minor and patch updates are typically backward compatible and can be applied without concern for breaking changes.<m>