It is common for developers to add npm packages in their applications without knowing what is going on behind the curtains, while also following other bad package.json practices. It is understandable, most of us work in fast-paced environments where we have strict dates to deliver new functionality for our clients. However, as developers, we might face security concerns if we undermine the risk of adding a new npm package, and we should also at the same time keep a healthy package.json. In this article you will learn about the main concerns about npm package management and what we can do to avoid them.
1. Avoid trivial packages
A trivial package is a package that does functionality that a developer can easily code themselves. Trivial packages are common, a study found that trivial packages make up 16.8% of their studied npm packages. Trivial packages are used because they are perceived to be well tested implementations of code, however only 45.2% of trivial packages even have tests.
This data is concerning considering the risk one takes when adding a new package who can be exploited. One example where adding a trivial package went wrong is the left-pad package. On 22 March 2016 its creator, Azer Koçulu, decided to unpublish several of his packages in npm, including left-pad. This led to thousands of applications breaking - even high-profile applications like React, Babel and Node. This package consisted of roughly 11 lines of code.
Tip: When adding a new package to your project, analyze the cost-benefit and evaluate if you can develop the same functionality yourself without impacting the timeline of the project. Each new package is a new potential point of failure, so it is wise to minimize failures as much as possible.
2. Use SemVer
To keep a healthy package.json, first we need to take a look at Semantic Versioning, or SemVer. Below is a chart given by npm explaining how SemVer works:
Basically, we start our product release version with 1.0.0. Then, the next release version number depends on the type of changes that we introduce.
Tip: It's recommended to use caret (^) to accept minor and patch updates, and tilde (~) to accept patch releases only. Depending on your project's needs, you may use these restrictions to limit the version of the package your project will use. Check SemVer cheatsheet for more information.
If we follow SemVer, we can handle major updates manually which are more likely to break our code. If you're not sure if you're following SemVer ranges correctly check the SemVer calculator.
3. Avoid pinning dependencies
Pinned dependencies are packages which are fixed package versions. This is not recommended since the package will not get latest updates.
"dependencies": {
"react": "16.8.6"
}
4. Avoid adding git repositories as packages
There is a way of adding git repositories as packages, but this should be mostly used as a last resort (for example, when the package doesn't exist in npm). If this repository is deleted or updated, you might face major issues.
"dependencies": {
"react": "facebook/react"
}
5. Look up the package overall health
A package health can be defined by three questions:
- Is this package actively maintained?
- How many developers use it?
- Which are the known security issues?
All this information can be found in GitHub, you can also look at the npm weekly downloads of the particular package you have chosen to see how frequently it is used among the dev community. There is also the Snyk Advisor tool, which shows all this information in a very friendly UI: https://snyk.io/advisor/
Package management sounds a bit complex, and every team approaches it differently. I hope these points have given you an insight on the most important things to keep in mind to maintain a healthy package.json. There is no perfect way of handling it, but when we know the most common points of failure, we can avoid security risks and keep our application working.
In this article my goal was to give an overview of the most common tasks we can do to manage our npm packages, if you wish to go more in depth I recommend reading Karen De Graaf's article "Guide to managing npm packages in your package.json"
References:
Rabe Abdalkareem, Olivier Nourry, Sultan Wehaibi, Suhaib Mujahid, and Emad Shihab. 2017. Why do developers use trivial packages? an empirical case study on npm. In Proceedings of the 2017 11th Joint Meeting on Foundations of Software Engineering (ESEC/FSE 2017). Association for Computing Machinery, New York, NY, USA, 385–395. https://doi.org/10.1145/3106237.3106267
How one programmer broke the internet by deleting a tiny piece of code
How one developer just broke Node, Babel and thousands of projects in 11 lines of JavaScript
Top comments (10)
Great Article, thank you :)
I saw that break changes happens In a minor version.
One of the packages is
antd
. I used a version4.18.5
ofantd
.Its latest version is
4.24.7
. I tried to upgrade the version for new features but there were break changes, so, I couldn't. Now, I'm using it as a fixed version. (At least, I could use tilde though). Are there better options in this sitation that I can take?Hi SeongKuk! Ideally, package mantainers should not push breaking changes in a minor version. Sometimes this happens and we can use a fixed version as a quick fix while we prepare our project to work with the newest package version.
This was out of the scope of this article, but since we depend on other's packages when using npm, we cannot always verify if the maintainers are following SemVer correctly :( we can do our part though, and be prepared when things don't go as planned :) Thank you for reading!
Thanks for reply :)
Another thing I do is delete the package-lock.json file and create it again.
Why? Sometimes when I uninstall a dependency it is removed from the package.json and from the package-lock.json but its sub-dependencies are not, so at the end in the "node_modules" folder reside these dependencies that I am no longer using.
And deleting the file and creating it again with "npm install" lightens up the "node_modules" folder for me.
In addition, another tip would also be to install only the dependencies that are going to be used.
Hi Leober! I also delete the package-lock.json because of the same reasons you mentioned, though I'm not 100% sure if this is the best way to remove the sub-dependencies or if it is a good practice.
I totally agree on adding only the dependencies that are going to be used! Thanks for your input :)
While I understand the intention here, this presents certain issues that need to be taken into account.
Sometimes package updates include breaking changes or new bugs. Therefore, if the version number isn't fixed in package.json, that means the code may break due to a package update.
Therefore, I would actually recommend to pin the dependencies. This also ensures that all the developers working on the project are using the same package versions thus providing the same features and running the same code. Otherwise, the developers are practically working on a different codebase.
A better practice to update packages would be to use a tool like npm-check-updates. Then to try and install the latest versions from time to time in a separate branch and check whether the code still works or if it breaks, fix whatever is necessary, and merge back to main. It's also possible to somewhat automate this in the CI/CD workflow with tools like Dependabot.
Hi Ido! I completely agree, in this article I tried to cover the basic points without going too deep. Ideally we would not have to worry about breaking changes if we follow SemVer correctly, but in reality there are package updates which introduce breaking changes which sometimes not even the package maintainers are aware of. In this case, it will be best to pin dependencies, but should be used as the last resort.
I disagree on always pinning all dependencies since the dependency tree will keep updating anyway, and any security fixes in the package updates will be missed. But these priorities change depending on the type of project we're working with. I think there is no right-wrong answer here, but a discussion on the matter is important.
Thank you for your input. I wasn't aware of the tools you have provided. Will take a look at them!
Thanks for the info... I will try implementing this.
good post! thanks😀
Thank you for reading :)