To enforce architecture boundaries in a TypeScript monorepo, represent the repository as a directed graph: projects or layers are nodes, imports are edges, and a policy determines which edges are legal. Then run that policy in CI. Nx provides a ready-made rule for project-tag constraints; a custom analyzer built on the TypeScript Compiler API gives you control over resolution, diagnostics, and specialized rules.
What an architecture boundary actually enforces
A boundary is a rule about dependency direction. For example, a billing feature may use shared utilities and billing UI components, but it must not import identity internals. A graph makes that relationship explicit:
As an Amazon Associate I earn from qualifying purchases.
- Nodes: applications, libraries, packages, or logical layers.
- Edges: static imports, re-exports, and package dependencies that your policy chooses to inspect.
- Policy: allowed and forbidden source-to-target relationships, plus restrictions on external packages.
The useful failure is specific: “libs/billing/src/invoice.ts imports libs/identity/src/token.ts; source tag scope:billing cannot depend on target tag scope:identity.” A list of files or a compiler error does not provide that architectural context.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Write the policy before choosing a tool. Decide which project types may depend on one another, which external packages each type may use, whether type-only imports count, and how aliases, re-exports, dynamic imports, generated files, and tests are treated. Nx describes this as tag-based dependency constraints and recommends a small, clearly defined vocabulary. Its overview explains the relationship between project tags, linting, and its broader Conformance approach.
#1 Best Overall
Choose an enforcement approach
| Option | Best fit | Boundary coverage | Trade-offs |
|---|---|---|---|
Nx @nx/enforce-module-boundaries |
Nx-organized JavaScript or TypeScript workspaces | Project imports and package dependencies, with tag constraints and external-package restrictions | Convenient lint integration; depends on correct Nx project metadata and tags. Oxlint support is documented as experimental. |
| TypeScript Compiler API analyzer | Specialized policies, custom output, or repositories without a suitable existing rule | Whatever dependency forms your implementation collects and resolves | Maximum flexibility, but your team owns policy configuration, graph completeness, resolution behavior, diagnostics, maintenance, and CI integration. |
| Nx Conformance | Nx Enterprise workspaces that need checks across project dependencies | Nx describes coverage of every project dependency, not only TypeScript imports or package.json dependencies |
Broader and language-agnostic scope; Enterprise qualification and the workspace’s dependency model must be verified. |
| dependency-cruiser | Standalone dependency-graph rules and cycle checks | Depends on configured rules and analyzer behavior | An alternative outside Nx; compare its resolution and rule coverage with the repository’s exact import forms. |
Compare tools on six practical dimensions: visible edge types, agreement with the build’s module resolution, central versus project-local configuration, language and workspace coverage, diagnostic quality and CI integration, and cost or vendor constraints. The available documentation does not provide a comparable performance benchmark, so measure runtime on your own repository rather than calling one approach fastest.
Define a vocabulary your team can apply
Tags should describe stable architectural meaning, not individual folders. A compact example is:
scope:billingandscope:identityidentify business domains.type:feature,type:ui, andtype:utilidentify library roles.
Keep the number of library types low. A tag that nobody applies consistently cannot protect a boundary. For each source category, write an allowed-target list and an external-package list. One possible policy might allow a billing feature to depend on billing UI and shared utilities, while a UI library may depend only on utilities. State forbidden targets as well when that makes an important prohibition obvious.
When several source tags match a project, Nx applies the matching constraints with AND logic. That means a project tagged both scope:billing and type:feature must satisfy the constraints for both tags, so review combinations deliberately.
Implement boundaries in an Nx workspace
1. Tag every project
Assign each application and library the smallest set of tags that describes its scope and role. Missing tags create an ungoverned project; contradictory tags create surprising intersections. Review tags as part of project creation and moves.
Rank #2
- TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
- TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
- Lightweight, Classic fit, Double-needle sleeve and bottom hem
2. Express the dependency matrix
The rule reference documents depConstraints and fields including onlyDependOnLibsWithTags, notDependOnLibsWithTags, allowedExternalImports, and bannedExternalImports. A representative configuration shape is:
{
"depConstraints": [
{
"sourceTag": "scope:billing",
"onlyDependOnLibsWithTags": ["scope:billing", "type:util"]
},
{
"sourceTag": "type:ui",
"notDependOnLibsWithTags": ["type:feature"]
},
{
"sourceTag": "scope:identity",
"bannedExternalImports": ["some-billing-only-package"]
}
]
}
The names above are illustrative; use the tags and packages that exist in your workspace. Keep the rule close to the normal ESLint configuration so local linting and CI execute the same check. The official rule reference lists current options and switches; it was marked last updated September 9, 2026.
Free tools Windows power users keep installed
One-click scans. No signup required.
3. Decide how exceptional edges are handled
Nx exposes an allow list and options related to circular dependencies, nested external imports, buildable-library dependencies, and dynamic dependencies. Treat these as temporary, reviewable exceptions. Record why an edge is allowed, keep the pattern narrow, and remove it when the migration or compatibility reason ends. A broad wildcard exception turns the rule into documentation rather than enforcement.
4. Put the check on the CI path
Run the boundary rule with the repository’s ordinary lint target and fail the job on violations. Developers should see the importing file, target project, and violated constraint in the lint output. Add a small fixture project for every important rule so a future tag change cannot silently weaken the matrix.
Build a custom analyzer with the TypeScript Compiler API
The Compiler API supplies the right foundation when your policy is more specific than a standard lint rule. TypeScript documents SourceFile as an abstract syntax tree representation and a Program containing command-line files plus files reached through references and imports in its compiler-API guide. Parsing an AST alone does not resolve every dependency or decide whether a dependency is legal; your analyzer must do those jobs.
1. Create the program from the repository configuration
Load the intended tsconfig rather than inventing compiler options. Use the same root files, path aliases, module target, base URL, and module-resolution settings that the build uses. If the repository has multiple configurations, create a program for each project or use the workspace’s project graph to associate files with their owners.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
2. Collect the edge forms your policy governs
Walk each SourceFile and collect static import declarations, export declarations with a module specifier, and import-equals forms when they are valid in the codebase. Decide explicitly whether import type counts; many architectural policies should count it because it still couples projects at compile time. Include dynamic import() only if your policy can resolve and classify it reliably. Do not imply that the Compiler API automatically makes this choice.
3. Resolve each specifier to a canonical target
Resolve relative paths, workspace aliases, package entry points, and declaration files using the same module-resolution rules as TypeScript. Normalize the result to a canonical file path so that ../identity/token and an alias pointing to that file become one graph target. If resolution fails, report it separately from a boundary violation; otherwise developers cannot tell a bad alias from a forbidden edge.
4. Attribute files to projects or layers
Map every resolved file to its owning project and architectural tags. Prefer explicit project metadata or workspace configuration over directory-name guesses. Decide how to classify files outside a project, generated output, tests, and declaration packages. An unresolved owner should produce a diagnostic or an intentionally documented exclusion, not an accidental pass.
5. Separate internal and external dependencies
Internal edges are checked against project or layer rules. External specifiers are checked against allowlists and denylists where the policy requires it. Keep package names canonical so a subpath such as package/internal cannot bypass a restriction intended for package.
6. Apply rules and emit actionable diagnostics
Represent each constraint as data, then evaluate every edge against the source project’s rules. A useful diagnostic includes:
- the importing file and owning project;
- the original module specifier and resolved target;
- source and target tags or layer names;
- the exact rule that failed; and
- an allowed alternative or remediation when one is known.
Exit nonzero when violations remain so the analyzer can run as a CI job. Keep output stable enough for pull-request annotations, but preserve a human-readable mode for local use.
7. Test the graph, not only the parser
Create fixtures for path aliases, re-exports, type-only imports, package boundaries, circular dependencies, unresolved modules, generated files, and every dynamic-import behavior you support. Test both allowed and forbidden edges. A parser test proves that a node was visited; a boundary test proves that the policy made the intended decision.
Handle edge cases deliberately
Aliases and barrel files
Aliases can hide a cross-project edge if the analyzer compares only raw text. Resolve aliases first, then classify the canonical target. Re-exports in barrel files are real graph edges and can expose an otherwise hidden dependency, so include them when the policy is about public project coupling.
Recommended Free Tools
Type-only imports
Type-only imports disappear from emitted JavaScript but still connect source projects and can create cycles or ownership leaks. Decide whether your architecture is about runtime loading, compile-time coupling, or both, and name that choice in the rule documentation.
Best Value
Dynamic imports
A dynamic import may be assembled from a variable and therefore cannot always be resolved statically. Choose one of three explicit treatments: resolve literal specifiers, forbid unresolved dynamic imports across boundaries, or allow them under a narrow exception. Nx documents options for dynamic-dependency exceptions; use them narrowly rather than ignoring all dynamic edges.
Generated and declaration files
Generated code can create noisy or misleading edges. Mark generated roots and decide whether they are analyzed, imported only through approved facades, or excluded with a documented ownership rule. Declaration files from external packages should not be mistaken for source projects.
Circular dependencies
Cycles are a separate question from directional layer rules, but they often reveal the same design problem. Nx exposes circular-dependency controls, and dependency-cruiser documents configurable graph rules and circular-dependency checks in its rules reference. Decide whether cycles fail immediately, are allowed within a layer, or require an explicit exception.
Where project references fit
TypeScript project references organize related compiler projects, can improve build times, and provide logical separation. The Project References handbook explains references and build mode. They are not a complete architecture policy: a referenced project can still be the wrong dependency for a business boundary, and references do not by themselves express external-package restrictions. Use them to structure compilation, then add explicit graph checks for the relationships your team cares about.
When broader dependency coverage matters
Import analysis sees edges represented by the forms you collect. A workspace may also have project dependencies that are not derived from TypeScript imports or direct package.json entries. Nx describes its Enterprise Conformance boundary rule as covering every project dependency and as complementary to the JavaScript/TypeScript lint rule. See the Conformance overview for its stated scope and Enterprise qualification. Confirm that model and licensing fit your workspace before adopting it.
Roll out enforcement without breaking every build
- Inventory first: generate a report of current edges and group violations by boundary, not by file.
- Fix the policy: remove ambiguous tags, split rules that mix unrelated concerns, and decide treatment for aliases, type-only imports, dynamic imports, tests, and generated code.
- Measure the baseline: keep existing violations in a temporary, reviewable list while new violations fail CI.
- Repair high-risk edges: replace deep imports with public entry points, move shared code into a genuinely shared utility, or invert a dependency through an interface.
- Delete exceptions: assign an owner and expiry reason to every allowlist entry.
- Review changes as architecture: require tag and constraint changes to receive the same scrutiny as source-code changes.
Troubleshoot common failures
- “The analyzer misses an import.” Check whether it is a re-export, type-only import, alias, package subpath, or dynamic import outside the collector’s scope.
- “A legal import is reported as forbidden.” Compare the analyzer’s resolved path and project ownership with the build’s module-resolution settings.
- “Only some projects are checked.” Look for missing tags, excluded roots, multiple
tsconfigfiles, or projects not included in the lint/CI target. - “Rules behave more strictly than expected.” Inspect projects carrying multiple source tags; Nx combines matching constraints with AND logic.
- “The graph is noisy after code generation.” Classify generated roots explicitly and keep generated imports behind approved boundaries.
Decision checklist
- Is the repository already organized as Nx projects with reliable tags? Start with
@nx/enforce-module-boundaries. - Do you need custom diagnostics, unusual dependency semantics, or a non-Nx workspace? Build on the Compiler API and own the complete resolution and test surface.
- Do you need language-agnostic, organization-wide project dependency checks in Nx? Evaluate Conformance and its Enterprise requirement.
- Do you primarily need configurable graph rules and cycle checks outside Nx? Evaluate dependency-cruiser against your exact import forms.
- Have you measured edge coverage and runtime on the actual repository? Documentation does not substitute for that local measurement.
Bottom line
AST import graphs are a practical enforcement mechanism when they are paired with an explicit policy and build-compatible module resolution. Use a project-integrated rule when your workspace metadata already describes ownership; use the Compiler API when you need specialized semantics and are prepared to maintain the collector, resolver, diagnostics, and tests. Project references improve compiler organization, but neither references nor AST parsing alone defines architecture. The durable control is a small vocabulary, a written dependency matrix, actionable failures, and a CI check that treats every exception as temporary.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →

