GSD
Strategies for Keeping Your Packages and Dependencies Updated
Posted by Camilo Reyes on October 20, 2023
Modern app development is defined, in part, by a vast array of frameworks and libraries one can work with. The proliferation of npm packages and dependencies can make one dizzy. In any given app, you may find hundreds of dependencies that are critical to the application. It is humbling to see dependencies now take up a larger footprint than the app itself. As developers rely on more sophisticated dependencies, it’s inevitable to see packages get out of control.
A slip-up in a dependency within a dependency, for example, can break the build or wreak havoc in production. You can test your own code all day long to make sure there are no errors. But if anything goes awry during the build or deployment phase, it is as bad as having logical errors in your own code.
So what can you do to make sure corrupt dependencies don’t creep up at the worst possible time? The deployment pipeline is just as critical as the code you author yourself. There must be a way to ensure safe and sound changes from the local dev machine all the way up to prod.
Below are four strategies I have found useful in enterprise software for maintaining dependencies.
Create your own private repo
You can create a private package repo that can live inside a corporate network. This adds a nice layer of protection against arbitrary dependency upgrades. The npm package ecosystem is one where dependencies may have one or more sub-dependencies. So anytime you bring in the latest packages, you run the risk of upgrades in a production server.
A good approach is to build a rock-solid pipeline that is consistent, from the local dev machine all the way up to prod. This shields you from surprises as you promote code changes up through production. The idea here is to test your changes using the exact same code and dependencies that will run for customers.
There are many private repositories available you can set up using your own server. This private repo can be fenced off from the public internet and a developer machine can target the private repo. If you don’t have your own server to host a private repo, npm offers a service you can use.
Using a private repo will help you avoid broken builds and even changes in behavior as you promote changes up to prod. Think of your dependencies as a critical part of the whole solution you deliver to customers. One unfortunate consequence of not having a private repo, for example, can be the left-pad and npm fiasco that broke many builds for people.
The only downside is having to specify which packages and versions will be included in the private repo. Your organization should give developers a way to add and update packages as they see fit. Consider making the process as clear as possible to avoid confusion and ad hoc programming around dependencies.
Lock down dependencies
Consider locking down dependencies through the package.json file. In npm, dependencies are inside a simple object that maps the package name to a specific version. Avoid ambiguity within dependencies and do not let npm do any guess work. The more precise you are with dependencies the safer deployments will be.
So, for example:
{ "dependencies" :
{ "foo" : "1.0.1" }
}
Computers are good at doing a specific job again and again but are not good at reading your mind. A better approach is to be as precise as possible and leave no room for guesswork.
For dependencies within dependencies, a good solution is to use the package-lock.json
file. Consider using this locking solution for getting identical and consistent dependency trees. This file will describe the exact chain of dependencies which makes package installations through npm install
portable and reproducible. You can commit this file into source control to get consistency across local dev machines and deployment pipelines.
If you are authoring an npm package, you may also use the npm-shrinkwrap.json
file to lock down dependencies. Unlike the previous package-lock.json
file, this file will be part of a published package. To create this file, use the npm shrinkwrap
command that comes with npm. Like any other dependency locking file, feel free to commit this into source control.
The npm package system has a nice clean way to lock down dependencies, so it’s to your benefit to use it.
Watch out for security vulnerabilities
One reason you must upgrade dependencies is to tackle security vulnerabilities. Keep in mind that the more dependencies you use, the larger the attack surface. To get a list of dependencies npm ls --prod
gets a list of packages that will run in prod. You may also add --long
to get a description of each dependency.
For example, to get a long list of prod dependencies:
You may find that you don’t use all dependencies. Your app may be carrying around extra packages that only serve to increase the attack surface. Consider carefully going through each dependency and shedding the extra baggage.
There are tools to check for unused dependencies such as depcheck. This tool goes through the package.json
file and flags any unused dependency that is not part of an import in the code. The command can be tweaked which makes it easy to automate as part of the deployment pipeline. One idea is to flag unused dependencies with an error and fail the build.
To check for security vulnerabilities, consider using Snyk. This tool will report on any dependency that has a known vulnerability. The command can be part of the build automation and flag security issues. As an alternative, keep an eye on the Node Security Platform. This is the definitive catalog for security vulnerabilities in npm packages.
As of April 2018, the Node Security Platform is part of npm, Inc. This shows there is a commitment to security from the npm registry.
As of npm version 6.x, you can find the command npm audit
. This uses the Node Security Platform which has a database of JavaScript vulnerabilities. This tool runs a security audit and flags any vulnerable dependencies.
The idea is to pick a good way to keep up to date with the latest updates on security. JavaScript has an ecosystem that is vibrant and very dynamic so keeping dependencies secure must be a high priority.
Understand semantic versioning
In packaged software, semantic versioning is a powerful tool. Semantic Versioning, or semver for short, gives you a good guide on how upgrades might impact the software. If you are looking to upgrade or author a package, keep this versioning system in mind which communicates changes.
You may notice that all npm packages come with three numbers separated by a dot. For example, 1.0.1
. On the surface, it may look like an arbitrary number tied to an internal build system. But, in most npm packages this is a form of communicating changes.
What you see in 1.0.1
, for example, are three things:
- MAJOR version meaning there may be backward-incompatible changes
- MINOR version meaning it has new features in a backward-compatible manner
- PATCH version meaning bug fixes and security patches
So, when an upgrade only updates the patch version number it means it’s safe to upgrade without any code changes. Patch version updates may have a security vulnerability patch so it’s good to go ahead and upgrade. Minor version updates come with new features, so it might be good to upgrade to use the new features. For major version updates, you want to be aware that it may come with a major rewrite. Consider going through the new features available for minor and major upgrades to see how you may benefit.
Semver is the golden standard when authoring npm packages. Be sure to use this way of communicating package changes to the JavaScript community.
Wrap-Up
To summarize, managing dependencies comes down to four important points:
- Creating your own private repo behind a corporate network
- Locking down dependencies
- Keeping an eye on security vulnerabilities
- Following semantic versioning
Change is hard and at times undesirable. Programming is not about clumsy or random changes but one of deliberate precision. One way to look at dependencies is to wield them with intent because these too are critical to the app. This leads you to a better path of building high-quality software.
All changes in any environment, whether it be in prod or another dev machine, must have consistent behavior. This helps in software that remains the exact same and is reproducible for testing. Your npm package strategy must make software that is immutable a high priority.
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Camilo Reyes is a husband, father, and software engineer living in Houston, Texas who is passionate about enterprise grade solutions and cybering all the things.