Intermediate workshop
NestJS Workshop: Building for Production
Mastering NestJS: From Basics to Advanced Application Design
NestJS, a progressive Node.js framework, has been gaining popularity for composing enterprise-grade server-side applications. Its out-of-the-box support for TypeScript, combined with features like dependency injection, decorators, and modular organization, makes it a go-to choice for developers aiming for clean, scalable code architectures.
Parallelly, Nx emerges as a powerhouse toolset for managing monorepos, facilitating code sharing and reuse across projects without losing sight of boundaries. Its ability to handle complex builds and dependencies across multiple frameworks and libraries streamlines development processes, especially in large-scale projects involving NestJS alongside other technologies like Angular.
In my journey over the past four years, NestJS has proven invaluable in maintaining solid code conventions and modular codebase. The last three years have seen Nx as a pivotal element in managing applications and libraries, ensuring consistency and efficiency in development workflows.
Note: You can follow the companion repository while reading this article.
Creating libraries within a NestJS application using Nx is more than a matter of convenience; it's about embracing a pattern that enhances maintainability and scalability. This synergy allows for workflows where code reuse becomes a norm, not an afterthought. The goal is to build libraries that are not just easy to use but also easy to maintain, a challenge many developers face in the lifecycle of a software project.
Nx brings to the table a set of tools that, when combined with NestJS's architecture, promote a level of abstraction and separation of concerns that is hard to achieve otherwise.
The process begins with initializing an Nx workspace configured to cater to a NestJS environment. This step sets the stage for a packaged-based monorepo structure where applications and libraries co-exist, sharing dependencies and tooling yet operating independently.
Question: What is a packaged-based monorepo?
The following command initializes an Nx workspace optimized for package management with npm, Nx also provides a preset for NestJS, which sets up the workspace with the necessary configurations for developing NestJS applications and libraries. Since we focus on libraries, we will use the @nx/nest plugin separately to generate libraries tailored for NestJS.
Intermediate workshop
Mastering NestJS: From Basics to Advanced Application Design
Within this workspace, we create libraries tailored for specific functionalities, such as interacting with external APIs or enhancing the application's core capabilities. The structure of these libraries is crucial, as it dictates their usability and ease of maintenance.
For example, your library could encapsulate the interactions with an Identity Provider, abstracting away the complexities of direct API calls and providing a simple, intuitive interface for the rest of the application. It could also contain a guard to check users' authentication and permissions in the NestJS application when importing your library. This encapsulation follows the principles of modular design, where each module or library has a well-defined responsibility and interface.
For instance, let's create two libraries: private-nestjs-library
public-nestjs-library
Note:
The
flag is used to generate a buildable and publishable library, while the --publishable
flag specifies the import path for the library. This is particularly useful when publishing the library to an npm registry. --importPath
The
flag is used to specify the directory where the library will be created. This is particularly useful when organizing libraries into subdirectories within the --directory
directory. packages
The
flag is used to ensure Nx only uses the provided project name and root format when generating the library files. --projectNameAndRootFormat
Now, let’s check the directory structure to be sure everything is correct by running tree . -d --gitignore
Which would translate to the following alias paths in tsconfig.base.json
By leveraging Nx's capabilities to generate buildable and publishable libraries, we ensure that each library can be independently developed, tested, and versioned, encouraging modularization and reusability.
private-nestjs-library:
public-nestjs-library:
As you can see, only the public-nestjs-library
nx-release-publish
private-nestjs-library
public-nestjs-library
public-nestjs-library
Intermediate workshop
Master Nx to enforce architecture, speed up your development workflow and improve code quality
This article focuses on the theoretical aspects of creating libraries with Nx and NestJS. In practice, it will depend on the purpose of the library and the provided functionalities; however, a typical pattern is to iterate over the following steps:
Create interfaces and constants to configure the module(s)
Declare a dynamic Module that the consuming application will import and take care of instantiating the providers and, eventually, controllers
Create a service that will handle the business logic and expose methods to be used by the consuming application
Write unit test suites for the service
When implementing your library, keep in mind best practices in software development, including but not limited to:
Abstraction and Encapsulation: Keeping implementation details hidden, exposing only necessary interfaces. This makes library usage and maintenance effortless.
Single Responsibility Principle: Each library should have one purpose and not be overloaded with functionalities that can be decoupled.
Dependency Injection: Facilitates testing and decouples the libraries from their dependencies.
Modularization: Promotes code reuse and simplifies the maintenance of large codebases.
A typical entry point of a NestJS library could look like this:
Resulting in the following Nx dependency graph:
One of the critical aspects of maintaining high code quality in a library-centric development environment is ensuring consistency and correctness in external dependency management. Nx addresses this with the @nx/dependency-checks
This tool automatically checks for missing dependencies, obsolete dependencies, and version mismatches. It's instrumental in ensuring that libraries are self-contained and their dependencies are accurately reflected and up-to-date, reducing integration and compatibility issues.
For our private library private-nestjs-library
However, the public-nestjs-library
private-nestjs-library
private-nestjs-library
private-nestjs-library
The solution in two parts is:
In packages/public-nestjs-library/.eslintrc.json
@nx/dependency-checks
In packages/public-nestjs-library/project.json
targets.build.options
"external": "none"
packages/public-nestjs-library/.eslintrc.json:
Finally, after running nx lint public-nest-library, the package.json is generated with the correct dependencies, but I suggest moving the functional dependencies to peerDependencies to avoid version conflicts with the hosting NestJS app.
If a configuration does this automatically, please share the info in the comments.
packages/public-nestjs-library/package.json:
Tips:
You can find some more in-depth information about
rule in the Nx docs. @nx/dependency-checks
Remove the
field if you whish to keep the library private. publishConfig
Using Nx with GitHub Actions is a powerful combination; in just few lines of code, you can end up with an efficient CI workflow that automates the testing of your libraries. And thanks to Nx, the verification only runs for the libraries affected by the current changes, saving you a lot of time and resources.
Tips: You can find more information about the Nx GitHub Actions and the Affected Commands in the official documentation.
The lifecycle of a library extends beyond its initial development. Managing versions and releases is a critical aspect of ensuring that improvements, bug fixes, and new features reach the consumers of the library in a controlled and predictable manner. With its release CLI, Nx introduces a streamlined approach to versioning and release management.
This tool simplifies bumping versions, generating changelogs, tagging releases, and publishing packages. It supports both independent and synchronized versioning strategies across the monorepo, accommodating the unique needs of each library within the workspace.
The flow is as follows:
I found this configuration particularly useful when using GitHub to manage releases and changelogs and NPM to publish packages. It will create independent changelogs and versions for each library.
nx.json:
Note:
The
field specifies the projects to be released, while the projects
field specifies the versioning strategy. In this case, the projectsRelationship
strategy is used to version each project independently and the independent
is explicitly excluded from the release process. private-nestjs-library
The registry URL is set to a local Verdaccio instance, which is a private NPM registry. You can replace it with the URL of your preferred NPM registry. Verdaccio can be started with the following command:
. node tools/scripts/local-registry.mjs
packages/public-nestjs-library/project.json:
Warning: In my previous experience,
target could be fully configured in nx.json file, but during the writing of this article nx-release-publish
had to be set on a project basis to trigger the build target before the release. dependsOn
And when it is time to release a new version, the following command gets the job done:
Tips: You can find more information about the Nx release CLI in the official documentation.
By leveraging Nx's release management capabilities, developers can ensure their libraries align with semantic versioning principles. This process makes tracking changes and managing dependencies easier but also integrates smoothly with continuous integration pipelines, ensuring that releases are consistent, reliable, and automated.
The combination of NestJS and Nx offers a robust framework for creating libraries that are not just powerful but also elegant and maintainable. By embracing the theoretical principles of modular design and best practices in software development, we can build a codebase that is both scalable and easy to manage. As the software development landscape evolves, the combination of NestJS and Nx provides a solid foundation for building sophisticated, enterprise-level applications.