NPM
Best Practices for Your Organization’s npm Packages
This article part of a series on Mastering npm in the Enterprise, also available as a chapter in our free, downloadable eBook
When using npm packages for your development in teams across an organization, you’ll want to set some standards, especially when they’re sharing libraries. A “wild west” of teams doing their own thing ends up in confusion and potential risks, like version conflicts, and even security issues.
Scouring the web for tried and tested practices won’t turn up much. Most sources are overly technical, and focus more on practices for OSS community repositories and packages, rather than your internal repositories within your organization.
In this article, I’ll outline practices to follow when using your own internal npm packages among your teams. We’ll talk about using metadata in the package.json, and organizing packages with scopes. We’ll also look at staying consistent with CI/CD (Continuous Integration and Continuous Deployment) which automates testing and deployment, as well as Semantic Versioning 2.0.0 (SemVer2).
Optimize Your Metadata
npm packages are bundles of code that teams can share and reuse across projects. Each package comes with a package.json file, which contains essential information like metadata, versioning details, and dependencies.
A common issue is that many in-house packages include a whole bunch of unclear metadata, making things way more complex than they ought to be. While a wide variety of metadata fields have their place in open-source packages on npmjs.org, it’s not always the case for internal packages.
Sure, these fields can be useful sometimes, but overloading the package.json can make it really hard to digest. Imagine trying to get your head around this:
{
"name": "example-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.17.1",
"extra-lib": "^1.0.0"
},
"description": "A project that does stuff.",
"author": "John Doe",
"keywords": ["example", "random", "test"],
"license": "MIT",
"main": "index.js",
"files": ["src", "README.md", "LICENSE"],
"peerDependencies": {
"react": "^17.0.0"
},
"scripts": {
"start": "node index.js",
"build": "webpack",
"randomTask": "echo 'Task needed for this'",
"lint": "eslint ."
},
"devDependencies": {
"jest": "^27.0.0",
"confusing-dependency": "^1.0.0"
},
"extra": {
"info": "This is unrelated info.",
"randomness": ["Cats can jump high!", "Pizza is great."]
}
}
Vague and uninformative descriptions, ambiguous scripts and unclear dependencies all add to the confusion, while some fields look like they’ve just been inserted wherever, with no rhyme or reason.
This is why you want to keep your metadata clear and organized —it’s way easier to understand, navigate, and work with, which makes life a lot simpler for everyone involved! You also want to make sure any private repositories are defined in the field publishConfig, and any dependencies are referencing packages by both name and version. Par exemple:
{
"name": "example-project",
"version": "1.0.0",
"description": "Utility functions for string manipulation.",
"author": "John Doe",
"license": "MIT",
"main": "index.js",
"keywords": ["string", "utility"],
"scripts": {
"start": "node index.js",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"jest": "^27.0.0",
"eslint": "^7.0.0"
},
"publishConfig": {
"access": "private", "registry": "https://your-private-repo.corp.local"
},
}
Now, isn’t that much nicer to read? By keeping metadata consistent and organized, packages are not only simple to maintain and understand, but waaay easier to reuse across different teams!
You’re not done yet though! It’s not just about keeping things simple —security matters too. This is why using “scopes” for your packages is a great way to keep things not only organized, but more secure.

Scope Your Packages
A “scope” in npm groups related packages under a common namespace, usually tied to an organization. Scopes are represented by a prefix with @, such as @my-org/package-name. Even if you’re not publishing packages to a public repository, I’d really recommend creating a scope and registering it on npmjs.org. This adds an important layer of security, making sure no one else can create packages under your organization’s name.
This can avoid risks like dependency confusion or typo-squatting, where malicious packages with similar names might be downloaded by mistake. Let’s say you don’t register @my-org. Someone else could then publish packages with names like @my-org/helpers that appear legit but actually contain harmful code. This takes advantage of users who assume all @my-org/* packages are safe. By registering your scope, you’ll set a clear identity for your organization and keep anyone from publishing packages under your name.
When it comes to scoping, you should scope your packages by organization, —not by individual teams. Create team-specific scopes and you’ll end up with way too many, making things harder to manage with inconsistent naming and complicated access control. So, I recommend sticking to one scope for your entire organization to keep things manageable.
How to Scope Your Packages
When you create a package, you can add a scope by simply naming it with the scope prefix:
npm init --scope=@my-org
This creates a package like @my-org/my-package, keeping under your organization’s umbrella. However, even if you’re using scopes, without proper versioning and release standards, things can still lead to confusion. This is where SemVer2 and CI/CD workflows come into play.
Use SemVer2 & CI/CD in Your Organization
Using CI/CD with Semantic Versioning 2.0.0 (SemVer2) helps keep npm package releases consistent and easy to follow. CI/CD automates the entire release process, ensuring packages are built, tested, and deployed without manual intervention. SemVer2, on the other hand, offers a clear, logical versioning scheme that anyone can follow.
Avoid Custom Tags
With your releases, you should stick to SemVer2’s versioning conventions, and avoid using “custom” distribution tags like alpha or beta.
The problem with tags is that they are server-side rather than part of a package’s metadata, and can be changed without notice. Suppose a developer has installed a package with the tag beta. If someone else decides to update that tag to point to a newer version that contains breaking changes, that developer will unknowingly receive that version the next time they update their dependencies. This can lead to crashes or malfunctioning features in their application.
The “Latest” Tag
One exception to this is the latest tag. This is a built-in npm tag that points to the most recent published version of a package. This can simplify installing tools or applications on your development machine or hosting server using npm, as you’ll always need the latest version you’ve released when deploying.
Pre-release Identifiers
So, if we aren’t using distribution tags for our releases, what are we going to do? The answer lies in SemVer2’s pre-release identifiers for testing and development phases. SemVer2 allows for identifiers, like -rc (release candidate):
"version": "1.0.0-rc.1"
Using these means not only can developers clearly see what version is what, but as packages are immutable, there can be no confusion about which versions are installed or used as dependencies.
Once you’re using pre-release identifiers, I recommend coming up with a standard set of them to keep things consistent in your organization. By using a consistent set of identifiers like -alpha, -beta, and -rc, everyone in the organization will be on the same page about the state of the software.
You can then leverage this standard in your CI/CD build scripts which can automatically tag releases according to these identifiers. Doing this will stop developers from using their own identifiers, make sure they stick to the established standard, and avoid any errors during deployment.
Key Practices for npm Package Management
A major problem for teams managing npm packages is a lack of clear standards or practices. Without these, different teams end up using their own approaches, leading to confusion, inefficiency, and potential security risks.
The solution lies in keeping things standardized. Stick to consistent metadata in package.json, use organization-wide scopes, and follow SemVer2 for clear versioning in your CI/CD workflows. Following these approaches will reduce confusion, and improve the security of your npm package libraries.
I’ve covered a lot today, and recommend keeping this info handy for future reference. I’ve compiled all this and more into an eBook “npm in the Enterprise“. You’ll not only learn more about npm scoping, versioning and dependencies, but also about vulnerabilities, licenses and auditing! Why not download your free copy today?