The Node.js Application Maintainer Guide
TLWR: maintaining a Node.js code base is essential to ensure its success in the long term. There are three main categories to maintain an application: dependencies maintenance, Node.js versions maintenance, and Main dependencies and architecture maintenance.
There is excellent content for starting the journey as a Node.js developer; you could find a handful of articles, courses, tutorials, and videos to help developers kickstart a project and guide them through the developing process.
You will also find a significant number of web entries guiding how to improve performance, diagnose problems, debug applications code, and many other appealing topics. Yet, that number starts dramatically decreasing when you want to search for guides on how to maintain a Node.js application sustainably.
Why would you need a guide for maintaining a Node.js application?
Node.js is a constantly evolving technology; you can evidence how active is its core development checking the Changelog section with a considerable amount of versions released; plenty of changes are described there. There are always fixes, deprecations, improvements, new APIs, and features starting as experimental, and most sure, those features and APIs will turn stable soon in future releases.
There is also another thing to be concerned about; Npm is a very fast-growing ecosystem with millions of modules, a paradise for developers saving time in their implementations using third-party packages. Those packages could solve their specific problems and give them back time to use it in their business logic instead of writing some standard functionalities. Those modules, though, come with a high price in the maintenance phase of Node.js applications as there are some trade-offs in using third-parties code.
Due to the situations described above, any individual or organization that wants to look forward to keeping a Node.js application healthy should elaborate a long-term strategy to do so.
Creating a basic framework for Node.js applications maintenance
When talking about maintaining a Node.js application, it is easy to identify at least three primary categories to act and concern:
- Dependencies maintenance
- Node.js versions maintenance
- Main dependencies and architecture maintenance
There could be more categories depending on the nature of the project, application type, architecture complexity, and the organizational processes involved, so we should consider this initial categorization as the foundation to create a custom categorizing system on top of these three.
Every category has its challenge and difficulty, which directly translates in time and effort developers need to put into those; let's do an overview of the work involved in each category.
Dependencies maintenance
This category is the most basic to keep an application healthy. Suppose a developer or an organization is limited in its time allowed for supporting tasks. In that case, this is definitely where the available time and resources need to be put, as this category is where a Node.js project will accumulate technical debt surprisingly fast, primarily due to the natural evolution of the OpenSource projects.
Lack of time for supporting this category could eventually translate into:
- Security issues: new vulnerabilities are discovered very often in the dependencies tree.
- Deprecations: Some new versions deprecate or change functionalities that could affect how the application uses the package in question.
- Licensing changes: Some OpenSource projects that get very used try to monetize and modify the license for their code, affecting the legal usage and creating legal liabilities.
- Obsolescence: Packages could lack maintenance and suffer code stagnation and keep using old abstractions depending on code that is no longer qualified as the current recommended way of solving the problem it was designed to solve.
There are reasonable solutions available in the ecosystem, helping to keep this rubric sane.
The no-brainer solution would be to monitor the dependencies and integrate a tool with the development cycle, checking the dependencies continuously. A solution we recommend for this is NodeSource Certified Modules 2, which is a security, compliance, and curation tool around the third-party dependencies. It is designed to add a layer of protection against known security vulnerabilities, potential licensing compliance issues, and general quality or risk assessment information for the modules used from the third-party ecosystem.
Dependabot and Renovate are other helpful tools creating pull requests when a new version is released for one of the third-party dependencies. This process could even be automatic under the right conditions; It is recommended to have good test coverage across the dependencies usage.
The final suggestion here is to take at least a couple of hours every developing cycle (a sprint if an organization uses Scrum) to monitor changes in the dependencies tree. Those two hours could be enough to perform the easy updates and estimate and schedule any significant effort required for major releases of the modules used in the application, which is common to introduce breaking changes or innovations in the way the code works.
Node.js versions maintenance
Knowing how to keep the Node.js version up-to-date is about understanding the Semantic versioning system (Semver) and the Node.js releases cycle.
There are two types of releases in Node.js:
- Current version: This is the most recent Node.js release stable version that will be supported and open to non-trivial changes until the next major release.
- LTS version: LTS is an acronym for Long-Term Support and is applied to release lines (major even-numbered versions like 10.x, 12.x, 14.x ...) that will be supported and maintained by the Node.js project for an extended period.
After understanding the version types, the obvious recommendation for production usage is to install an LTS version.
Now, let's explain the concept of support for LTS versions. There are two types of support levels for these versions:
Active LTS: is a release line that is being actively maintained and upgraded, including backporting newer non-breaking features, functionality, and improvements, addressing bugs, and patching security vulnerabilities.
Maintenance LTS: is a release line that's nearing End of Life (EOL) and will only receive bug fixes and security patches for a short time. If the developer or the organization is not tied by any restriction like old operating systems not supported, the suggestion here would be to always stay in the Active LTS version; This status changes every year around October with every new version.
Here is the recommended process to move an application safely to the next LTS:
- Schedule tests with the Current version (The one that will become the next LTS) for the application in early September every year (usually a new LTS launches every October). The main goal for these tests is to discover and schedule all necessary work the application would require to run the upcoming LTS.
- Here is a checklist to go across with: Make sure all dependencies properly install with the new version in a clean npm install.
- Start the application and look for deprecations warnings to identify dependencies or the application code using deprecated APIs in the upcoming LTS version. A complete list of deprecated APIs can be found here
- Run the application tests suites and see if those pass without issues; if something fails, analyze and schedule any required work in a subsequent development cycle.
Perform all tasks identified in the first step to make the application compatible with the upcoming LTS version; also modify and add tests when needed to cover those changes.
Put two versions of the application in a Staging environment, the first version running the same Node.js used in the production environment and the other version running the current Node.js version (the one that is becoming the newest LTS soon), and run load tests against both to compare the performance. Usually, the test would show a performance improvement with the latest version; if there is a performance degradation with that one, a performance analysis to identify and fix the culprit would be needed.
Wait for the official LTS release and schedule the move to production.
Main dependencies maintenance
In any Node.js application created for production usage, in almost every case, there is always the main dependency or dependencies broadly used that shapes the architecture of the entire codebase as most of the code written uses its conventions, functions, and tooling.
In the REST APIs case would be the web framework (Express, Hapi, Fastify...), in GraphQL API cases would be the GraphQL server (express-graphql, Apollo, Hasura, Prisma....), for a CLI application, the argument parser (Commander, Minimist, Yargs...); so on.
These dependencies should be pretty well identified in every application and should be maintained as described in the first category in this guide. However, the ecosystem's health and space for these specific dependencies are recommended to be evaluated at least every two years.
There is an inherited risk of stagnation of the applications stack due to changes in the dependency ecosystem, or there is also the case that there could be new dependencies in the space that satisfy the same need in a better or more performant way.
One good example at this time would be to evaluate moving from Express.js ecosystem, which is stuck and just receiving necessary patches (Express 5 has been pending for more than four years already), to a more actively developed option like Fastify, which exceeds in performance.
The process of making significant changes to an application's stack should be carefully weighted; these type of changes would probably mean a considerable amount of work across the whole codebase; here is a list of questions to answer helping with the decision:
Is the current dependency healthy maintained? Answer the question by checking all versions released in the last year and new features added in those. If the number of versions is three or fewer and not even a new feature added, that should be a matter of concern.
Is there a new dependency doing what the application needs to do in a better way? To solve this question, the developer or the organization needs to do a comprehensive analysis of the current dependency niche.
Take actions like researching the dependency's space, checking specialized blog posts, online conferences, dig in StackOverflow, checking newsletters, and using sites like NPM Trends and NPMCompare to identify possible candidates.
If there is the case that the current dependency is falling behind and there is a suitable dependency to replace it with, the recommended approach would be to evaluate aspects like:
- Development process: how is this new dependency developed?
- Architectural implementation: is this a compatible architecture with the application to maintain?
- Maturity of the ecosystem: are enough tools available, plugins, documentation, benchmarks, and success stories using this new dependency?
- Performance: build a probe of concept implementing a small part of the application with the new dependency and load test it, comparing results against another load test of the same section of the application with the current dependency, is your application performing better (more operations per second, using fewer resources, faster load times) with the new dependency?
Following the previous process will provide a clear idea and figure out if it is time for the application to move on from any heavy-used dependency falling short. It will also give the developer or the organization the proper knowledge to estimate the required effort for migrating it.
Creating a custom made maintenance plan
As stated before, these three categories cover general aspects to consider when maintaining Node.js applications; there could be many other categories to include depending on organizational challenges and even human processes involved in every day of Node.js enterprise development.
Use these categories as a foundation or framework to create your suitable strategy. Some organizations, due to their size, and human processes involved, could find this challenging.
We, here at NodeSource, have seen many of these cases where the organization can not find the right path to create a sustainable process to maintain their applications; we have a lot of experience solving these specific issues. Get in touch here; we would be happy to help you achieve a successful maintainable status for your codebases.