Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › architecture

Building TypeScript Architecture Boundaries with AST Import Graphs

By Freedom251 Team · · 9 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Write 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.

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:billing and scope:identity identify business domains.
  • type:feature, type:ui, and type:util identify 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 Programming Language - Software Engineer & Coder T-Shirt
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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

  1. Inventory first: generate a report of current edges and group violations by boundary, not by file.
  2. 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.
  3. Measure the baseline: keep existing violations in a temporary, reviewable list while new violations fail CI.
  4. 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.
  5. Delete exceptions: assign an owner and expiry reason to every allowlist entry.
  6. 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 tsconfig files, 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

architectureASTCICompiler APIDependency Analysisimport graphsmonoreposNxTypeScript

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.