If you partition your code into well-defined cohesive units, even a small organization will end up with a dozen apps and dozens or hundreds of libs. If all of them can depend on each other freely, chaos will ensue, and the workspace will become unmanageable.
To help with that, Nx provides powerful mechanisms to enforce architectural boundaries and ensure projects can only depend on each other according to your organization's rules. You can declaratively define constraints using project tags and enforce them automatically.
How to Enforce Boundaries
Section titled “How to Enforce Boundaries”Nx offers two complementary approaches to enforce module boundaries:
ESLint Integration - For JavaScript/TypeScript projects, enforce boundaries on code imports using the @nx/enforce-module-boundaries
ESLint rule. This checks TypeScript imports and package.json
dependencies during linting.
Language-Agnostic Conformance - For any project type (e.g. Java, Python, PHP, JavaScript, etc.), use the Conformance plugin's Enforce Project Boundaries rule. This rule checks dependencies in the Nx graph during nx conformance:check
. Requires Nx Powerpack or Enterprise.
Both approaches use the same tag-based constraint system described below.
Nx comes with a generic mechanism for expressing constraints on project dependencies: tags.
First, use your project configuration (in project.json
or package.json
) to annotate your projects with tags
. In this example, we will use three tags: scope:client
. scope:admin
, scope:shared
.
{ // ... more project configuration here "nx": { "tags": ["scope:client"] }}
{ // ... more project configuration here "nx": { "tags": ["scope:admin"] }}
{ // ... more project configuration here "nx": { "tags": ["scope:shared"] }}
{ // ... more project configuration here "tags": ["scope:client"]}
{ // ... more project configuration here "tags": ["scope:admin"]}
{ // ... more project configuration here "tags": ["scope:shared"]}
Configure Boundary Rules
Section titled “Configure Boundary Rules”Once you have tagged your projects, configure the dependency constraints based on your chosen approach:
For JavaScript/TypeScript projects, configure the @nx/enforce-module-boundaries
ESLint rule:
nx add @nx/eslint-plugin @nx/devkit
And configure the rule in your ESLint configuration:
import nx from '@nx/eslint-plugin';
export default [ ...nx.configs['flat/base'], ...nx.configs['flat/typescript'], ...nx.configs['flat/javascript'], { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], rules: { '@nx/enforce-module-boundaries': [ 'error', { /* options */ }, ], }, },];
{ // ... more ESLint config here
// @nx/enforce-module-boundaries should already exist within an "overrides" block using `"files": ["*.ts", "*.tsx", "*.js", "*.jsx",]` "@nx/enforce-module-boundaries": [ "error", { "allow": [], // update depConstraints based on your tags "depConstraints": [ { "sourceTag": "scope:shared", "onlyDependOnLibsWithTags": ["scope:shared"] }, { "sourceTag": "scope:admin", "onlyDependOnLibsWithTags": ["scope:shared", "scope:admin"] }, { "sourceTag": "scope:client", "onlyDependOnLibsWithTags": ["scope:shared", "scope:client"] } ] } ]
// ... more ESLint config here}
If you violate the constraints, you will get an error when linting:
A project tagged with "scope:admin" can only depend on projectstagged with "scoped:shared" or "scope:admin".
Read more about ESLint rule options.
For any project type or to enforce boundaries on the complete dependency graph, use the Conformance plugin:
nx add @nx/conformance
Configure rules in your nx.json
:
{ "conformance": { "rules": [ { "rule": "@nx/conformance/enforce-project-boundaries", "options": { "depConstraints": [ { "sourceTag": "scope:shared", "onlyDependOnProjectsWithTags": ["scope:shared"] }, { "sourceTag": "scope:admin", "onlyDependOnProjectsWithTags": ["scope:shared", "scope:admin"] }, { "sourceTag": "scope:client", "onlyDependOnProjectsWithTags": ["scope:shared", "scope:client"] } ] } } ] }}
Run conformance checks in CI:
- name: Enforce all conformance rules run: npx nx conformance:check
Learn more about Conformance rules.
With these constraints in place, scope:client
projects can only depend on projects with scope:client
or scope:shared
. And scope:admin
projects can only depend on projects with scope:admin
or scope:shared
. So scope:client
and scope:admin
cannot depend on each other.
Projects without any tags cannot depend on any other projects. The exception to this rule is by explicitly allowing all tags (see below).
Tag formats
Section titled “Tag formats”*
: allow all tags
Example: projects with any tags (including untagged) can depend on any other project.
{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"]}
string
: allow exact tags
Example: projects tagged with scope:client
can only depend on projects tagged with scope:util
.
{ "sourceTag": "scope:client", "onlyDependOnLibsWithTags": ["scope:util"]}
regex
: allow tags matching the regular expression
Example: projects tagged with scope:client
can depend on projects with a tag matching the regular expression /^scope.*/
. In this case, the scope:util
, scope:client
, etc. are all allowed tags for dependencies.
{ "sourceTag": "scope:client", "onlyDependOnLibsWithTags": ["/^scope.*/"]}
glob
: allow tags matching the glob
Example: projects with a tag starting with scope:
can depend on projects with a tag that starts with scope:*
. In this case scope:a
, scope:b
, etc are all allowed tags for dependencies.
{ "sourceTag": "scope:*", "onlyDependOnLibsWithTags": ["scope:*"]}
Globbing supports only the basic use of *
. For more complex scenarios use the regex
above.