Creating publishable NestJS Libraries with Nx
Tags: tutorial, nestjs, javascript
Published: 2024-04-15 14:01:00

---
title: Creating publishable NestJS Libraries with Nx
published: true
description: Powerful patterns to build your next NestJS libraries with Nx
tags: tutorial, nestjs, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ica5r19muolq840qm615.png
published_at: 2024-04-15 14:01 +0000
canonical_url: https://push-based.io/article/creating-nestjs-libraries-with-nx
---

[NestJS](https://nestjs.com), 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](https://nx.dev) 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](https://github.com/getlarge/create-nestjs-libraries-with-nx) while reading this article.

## The Synergy Between NestJS and Nx

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.

## Workspace setup

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](https://nx.dev/getting-started/tutorials/package-based-repo-tutorial)?

The following command initializes an Nx workspace optimized for package management with npm, Nx also provides a [preset for NestJS](https://nx.dev/nx-api/nest#setting-up-nxnest), 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.

```bash
npx create-nx-workspace@18.1.2 your-workspace --preset=npm \
--no-interactive --nxCloud=skip --yes

cd your-workspace

# you can check the docs https://nx.dev/nx-api/nest to know more about its capabilities
npm install -D @nx/nest

```

### Structuring the Workspace for Library Development

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` and `public-nestjs-library`. The former is an internal library not meant to be published in an NPM registry, while the latter is a public library intended for external consumption.

```bash
nx g @nx/nest:lib private-nestjs-library --directory packages/private-nestjs-library \
--buildable --importPath @your-npm-scope/private-nestjs-library \
--projectNameAndRootFormat as-provided --no-interactive

nx g @nx/nest:lib public-nestjs-library --directory packages/public-nestjs-library \
--publishable --importPath @your-npm-scope/public-nestjs-library \
--projectNameAndRootFormat as-provided --no-interactive
```

> **Note**
>
> - The `--publishable` flag is used to generate a buildable and publishable library, while the `--importPath` flag specifies the import path for the library. This is particularly useful when publishing the library to an npm registry.
> - The `--directory` flag is used to specify the directory where the library will be created. This is particularly useful when organizing libraries into subdirectories within the `packages` directory.
> - The `--projectNameAndRootFormat` flag is used to ensure Nx only uses the provided project name and root format when generating the library files.

Now, let’s check the directory structure to be sure everything is correct by running `tree . -d --gitignore` in the terminal. You should see something like this:

```sh
├── packages
│   ├── private-nestjs-library
│   │   └── src
│   │       └── lib
│   └── public-nestjs-library
│       └── src
│           └── lib
└── tools
    └── scripts
```

Which would translate to the following alias paths in `tsconfig.base.json`:

```json
{
  "compilerOptions": {
    // ...
    "paths": {
      "@your-npm-scope/private-nestjs-library": [
        "packages/private-nestjs-library/src/index.ts"
      ],
      "@your-npm-scope/public-nestjs-library": [
        "packages/public-nestjs-library/src/index.ts"
      ]
    }
  }
}
```

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:**

![private-nestjs-library](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/130kdwwd95vxfr5r1x15.png)

**public-nestjs-library:**

![public-nestjs-library](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86u5kpuewsi9lyb538e4.png)

> **Tips**
> If you wonder how to visualize this project view, run the `nx show project <project_name> --web`

As you can see, only the `public-nestjs-library` contains the `nx-release-publish` target, which triggers the library publishing to an NPM registry. Our plan is that `private-nestjs-library` will be imported and shipped with the `public-nestjs-library`, while the `public-nestjs-library` will be published and used by other applications.

## Library Implementation

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:

1. Create interfaces and constants to configure the module(s)
2. Declare a dynamic Module that the consuming application will import and take care of instantiating the providers and, eventually, controllers
3. Create a service that will handle the business logic and expose methods to be used by the consuming application
4. 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:

```ts
// packages/public-nestjs-library/src/lib/public-nestjs-library.module.ts
import { Module } from '@nestjs/common';

import { PrivateNestjsLibraryModule } from '@your-npm-scope/private-nestjs-library';

import { PublicNestjsLibraryOptions } from './public-nestjs-library.interfaces';
import { PublicNestjsLibraryService } from './public-nestjs-library.service';

@Module({
  imports: [PrivateNestjsLibraryModule],
  controllers: [],
  providers: [PublicNestjsLibraryService],
  exports: [PublicNestjsLibraryService],
})
export class PublicNestjsLibraryModule {
  static forRoot(
    options: PublicNestjsLibraryOptions,
    isGlobal?: boolean
  ): DynamicModule {
    return {
      module: PublicNestjsLibraryModule,
      imports: [PrivateNestjsLibraryModule],
      providers: [
        { provide: PublicNestjsLibraryOptions, useValue: options },
        PublicNestjsLibraryService,
      ],
      exports: [PublicNestjsLibraryOptions, PublicNestjsLibraryService],
      global: isGlobal,
    };
  }
}
```

Resulting in the following Nx dependency graph:

![Project graph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr1l7vihkzya0abu9tx9.png)

## Dependencies Management

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` lint rule, which helps keep the integrity and consistency of package.json files across the workspace.

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`, the setup is straightforward:

```json
// packages/private-nestjs-library/.eslintrc.json
// ...
  {
    "files": [
      "*.json"
    ],
    "parser": "jsonc-eslint-parser",
    "rules": {
      "@nx/dependency-checks": [
        "error",
        {
          "buildTargets": ["build"],
          "checkMissingDependencies": true,
          "checkObsoleteDependencies": true,
          "checkVersionMismatches": true
        }
      ]
    }
  },
  // ...
```

However, the `public-nestjs-library`, depends on `private-nestjs-library` that should remain internal. As a result, Nx needs to include the `private-nestjs-library` as part of the bundle, not as an external dependency. It should also include dependencies of the `private-nestjs-library`.

The solution in two parts is:

1. In `packages/public-nestjs-library/.eslintrc.json`, update `@nx/dependency-checks`, this time using **includeTransitiveDependencies** and **ignoredDependencies**
2. In `packages/public-nestjs-library/project.json`, update the `targets.build.options` to set `"external": "none"`

**packages/public-nestjs-library/.eslintrc.json:**

```json
// packages/public-nestjs-library/.eslintrc.json
// ...
  {
    "files": [
      "*.json"
    ],
    "parser": "jsonc-eslint-parser",
    "rules": {
      "@nx/dependency-checks": [
        "error",
        {
          "buildTargets": ["build"],
          "checkMissingDependencies": true,
          "checkObsoleteDependencies": true,
          "checkVersionMismatches": true,
          "includeTransitiveDependencies": true,
          "ignoredDependencies": [
            "@your-npm-scope/private-nestjs-library"
          ]
        }
      ]
    }
  },
  // ...
```

**packages/public-nestjs-library/project.json:**

```json
{
  "targets": {
    "build": {
      "executor": "@nx/node:build",
      "options": {
        "outputPath": "dist/packages/public-nestjs-library",
        "external": "none"
      }
    }
  }
}
```

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:**

```json
{
  "name": "@your-npm-scope/public-nestjs-library",
  "version": "0.0.1",
  "publishConfig": {
    "access": "public"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  },
  "peerDependencies": {
    "axios": "1.6.8"
  },
  "type": "commonjs",
  "main": "./src/index.js",
  "typings": "./src/index.d.ts"
}
```

> **Tips**
>
> - You can find some more in-depth information about `@nx/dependency-checks` rule in the Nx [docs](https://nx.dev/nx-api/eslint-plugin/documents/dependency-checks#dependency-checks-rule).
> - Remove the `publishConfig` field if you whish to keep the library private.

## Continuous Integration

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.

```yaml
name: CI
on:
  push:
    branches:
      - main
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-CI

env:
  CI: true

jobs:
  main:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci

      - uses: nrwl/nx-set-shas@v4

      - uses: 8BitJonny/gh-get-current-pr@2.2.0
        id: current-pr

      - if: steps.current-pr.outputs.number != 'null' && github.ref_name != 'main'
        # This line is needed for nx affected to work when CI is running on a PR
        run: git branch --track main origin/main

      - run: npx nx format:check

      - run: npx nx affected -t lint,test,build --parallel=3
```

> **Tips**
> You can find more information about the [Nx GitHub Actions](https://nx.dev/ci/intro/tutorials/github-actions) and the [Affected Commands](https://nx.dev/core-tutorial/04-build-affected-projects#affected-t) in the official documentation.

## Versioning and Release Management

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:

![nx release flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28xukvrgesf24ggmec8e.png)

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:**

```json
// ...
  "release": {
    "projects": ["packages/*", "!packages/private-nestjs-library"],
    "projectsRelationship": "independent",
    "changelog": {
      "projectChangelogs": {
        "createRelease": "github"
      }
    },
    "version": {
      "conventionalCommits": false,
      "generatorOptions": {
        "skipLockFileUpdate": true,
        "currentVersionResolver": "git-tag"
      }
    },
    "git": {
      "commit": true,
      "tag": true
    }
  }
  "targetDefaults": {
    // ...
    "nx-release-publish": {
      "options": {
        "packageRoot": "dist/packages/{projectName}",
        "registry": "http://localhost:4873/"
      }
    }
    //...
  }
```

> **Note**
>
> - The `projects` field specifies the projects to be released, while the `projectsRelationship` field specifies the versioning strategy. In this case, the `independent` strategy is used to version each project independently and the `private-nestjs-library` is explicitly excluded from the release process.
> - 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:**

```json
{
  // ...
  "targets": {
    // ...
    "nx-release-publish": {
      "dependsOn": ["build"]
    }
  }
}
```

> **Warning**
> In my previous experience, `nx-release-publish` target could be fully configured in `nx.json` file, but during the writing of this article `dependsOn` had to be set on a project basis to trigger the build target before the release.

And when it is time to release a new version, the following command gets the job done:

```bash
# for the first time you need to run the following command to create the initial tag
npx nx release --first-release

# after that, you can run the following command to release a new version
npx nx release
```

> **Tips**
> You can find more information about the Nx release CLI in the [official documentation](https://nx.dev/nx-api/nx/documents/release#release).

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.

## Conclusion

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.