The Node.js ecosystem is a vast landscape, and one of the most important parts of that ecosystem is its various modules. Installing these modules is an easy process, thanks to the npm registry. It is easy to download packages, create your own module, and publish it to the npm registry. The Node.js reference architecture team has recommendations for creating and publishing your modules.
Follow the series:
- Part 1: Overview of the Node.js reference architecture
- Part 2: Logging in Node.js
- Part 3: Code consistency in Node.js
- Part 4: GraphQL in Node.js
- Part 5: Building good containers
- Part 6: Choosing web frameworks
- Part 7: Code coverage
- Part 8: Typescript
- Part 9: Securing Node.js applications
- Part 10: Accessibility
- Part 11: Typical development workflows
- Part 12: npm development
- Part 13: Problem determination
- Part 14: Testing
- Part 15: Transaction handling
- Part 16: Load balancing, threading, and scaling
- Part 17: CI/CD best practices in Node.js
- Part 18: Wrapping up
Package development
When starting the creation of a new package, it is recommended to use the npm init command instead of hand coding to quickly and accurately create a new package.json. You can even accept all the defaults by providing the -y flag when running the following command:
npm init -y
This command can be modified to tailor the results to your specific organization's needs. To read more about this feature, check out the official npm documentation.
While name and version are the only fields that are required to publish a package, the team recommends completing a few other fields to provide more information to the user. The following sections provide details about those fields as well as other recommendations. To see the full list, check out the npm package development section of the Node.js reference architecture.
The files field
The files field is a list of files that you want to be published with your package. This field is handy when using a bundler to transpile code, and you only want to include that transpiled code. The team recommends not to include tests, which should reduce the package size. But you can include docs. For a list of the files that are automatically included, check out the npm docs.
The support field
The support field will help package maintainers communicate with and set expectations for their users about the level of support they are willing to provide on a package. The team has worked closely with the Node.js Package Maintenance Team on their recommendations for what this support field should look like. For our opossum module, we set this field to true and supplied our support information in a separate package-support.json file.
For more information on the support field, read the Package Maintenance Team statements.
The type field
The type field defines the module format that Node.js will use. For ES Modules (ESM), use module. For Common JS (CJS) modules, use commonjs. Adding this For more information on how Node.js determines the difference between ESM and CJS modules take a look at the Node.js official docs here
Specify a license
You should specify a license for your package so that people know how they are permitted to use it and any restrictions you place on it. The team has the most experience with using both the MIT and Apache-2 licenses. But if you are publishing a package on behalf of a company, it is recommended to consult your organization about their preferred licenses.
If a package is going to be unlicensed, it is recommended to set the private field to true. You can find more information on licenses and how they relate to the package.json.
The main field
This is the primary entry point to your package that should be a module relative to the root of your package folder. Most of the time, the default will be index.js if the main field is not set.
The scripts property
This property is an array containing script commands run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point. The team recommends creating scripts that handle calling the tests, linters, and any build steps that might need to occur. The team recommends not using a postInstall script if possible, as this could be a security risk.
Advice for choosing dependencies
Another important part of package development is the dependencies. The team has created a resource for choosing and vetting dependencies. This is also discussed in the 8 elements of securing Node.js applications article.
During package development, it is helpful to add package-lock.json to source control, so all developers working on the package can install the same versions of dependencies. It is also recommended to know what semver range you are specifying for any dependency you wish to install.
Here are a few examples:
- The ^ (carrot) will give you all the minor and patch releases when they become available.
- The ~ (tilde) will include everything greater than a particular version in the same minor range.
- The semver calculator is a great resource to assist in making this choice.
Recommendations for publishing modules to the npm registry
To publish modules to npm, the team recommends that you turned on 2-factor authentication and use automation tokens for those modules published with some type of Continuous Integration/Continuous Delivery workflow. The following sections provide recommendations for preparing your code, transpiling sources, and module versioning.
Preparing code
The team believes that it is important to keep your published packages tidy. Here are a couple ways to do this:
- Specify ignore files in a .npmignore.
- Specify the files you only want to publish with the files property in your package.json.
As for tests and documentation, the team has no clear recommendations. For modules our team publishes, we exclude the tests, and we publish any API docs that go beyond README to GitHub pages.
Transpiling sources
A package written in another language like TypeScript should be published with transpiled JavaScript as the primary executable.
Generally, the original sources should not be required to use your published package, but it's your preference whether to publish them or not. Similar to the guidance on tests, the modules that the team publishes that are transpiled only contain the generated code.
If your module requires build steps, it is recommended to run those before you publish and not when the user installs the package. This is where you would use the prePublish script as mentioned above.
It is important, if you are using Typescript or generating type definitions for your package, that you add a reference to the generated type file in your package.json and ensure that the file is included when publishing.
Module versions
We recommend using Semantic Versioning when possible. This will make it easier for users to determine if a new module version will potentially break their code.
There are two automation tools that can help with bumping your module to the correct version. These tools will base the version bump on commit messages that follow the conventional commit standard:
The npm CLI also provides a version command that will increase the package version. You can find all the recommendations for publishing on the Npm Publishing Guidelines GitHub page.
The npm proxy/mirror technique
The npm registry is great because it allows users to easily install third-party modules with just a few commands. But there could be some additional concerns if you are part of an organization that limits internet access or you are worried that a module you depend on could potentially disappear from the public registry.
This is where having a layer between your organization and the public npm registry can help. This is commonly referred to as an npm proxy/npm mirror.
In these situations, the team recommends using a proxy/mirror when possible. The following is a list of various scenarios where you might consider using this technique:
- You need to limit the installation of modules to only a specific set.
- You have limited network access.
- Using a proxy/mirror can provide a centralized point for scanning security vulnerabilities.
- A mirror can reduce the dependency on the public registry.
- You need to maintain a copy of a module in case it is removed from the public registry.
- You are a good npm citizen. The public registry is a free service, and npm allows updates to 5 million requests per month, which can be used up quickly with CI builds.
The proxy/mirror techniques can be used in two ways:
- For a more global solution, set the registry using the npm CLI:
npm set registry URL
- Use a .npmrc file per project if you need more fine-grained control.
You can find more information about npm proxies and mirrors in this section of Node.js reference architecture on GitHub.
The npm registry makes installing modules easy
As previously mentioned, this is just a subset of the full list of our team's advice and recommendations for installing and publishing modules to the npm registry. For the full list of recommendations, check out the npm package development section of the Node.js reference architecture.
We cover new topics regularly as part of the Node.js reference architecture series. Next, learn how to investigate 7 common problems in production.
We invite you to visit the Node.js reference architecture repository on GitHub, where you can view our work. To learn more about what Red Hat is up to on the Node.js front, check out our Node.js page.
Last updated: January 9, 2024