A TypeScript/JavaScript architecture testing library that allows you to specify and assert architecture rules in a fluent, expressive API.
Inspired by ArchUnit for Java
Installation • Quick Start • API Docs • Examples • Contributing • FAQ
ArchUnit-TS helps you maintain clean architecture in your TypeScript and JavaScript projects by:
- Enforcing architectural rules as executable code
- Detecting violations early in your CI/CD pipeline
- Documenting architecture through executable specifications
- Preventing architectural drift over time
- ✅ Fluent API for defining architecture rules
- ✅ Naming conventions enforcement
- ✅ Package/module dependency rules
- ✅ Decorator/annotation checking
- ✅ Layered architecture support
- ✅ Cyclic dependency detection
- ✅ Custom predicates for flexible class filtering
- ✅ Dependency graph visualization (interactive HTML and Graphviz DOT formats)
- ✅ Report generation (HTML, JSON, JUnit XML, Markdown)
- ✅ CLI tool for command-line usage
- ✅ Watch mode for automatic re-checking on file changes
- ✅ Severity levels (errors vs warnings) for flexible enforcement
- ✅ TypeScript & JavaScript support
- ✅ Integration with Jest and other test frameworks
- ✅ Zero runtime dependencies in production
npm install --save-dev archunit-tsor
yarn add --dev archunit-tsimport { createArchUnit, ArchRuleDefinition } from 'archunit-ts';
describe('Architecture Tests', () => {
it('services should reside in services package', async () => {
const archUnit = createArchUnit();
const rule = ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Service')
.should()
.resideInPackage('services');
const violations = await archUnit.checkRule('./src', rule);
expect(violations).toHaveLength(0);
});
});import { ArchRuleDefinition } from 'archunit-ts';
// Classes ending with 'Controller' should reside in controllers package
const controllerRule = ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Controller')
.should()
.resideInPackage('controllers');
// Classes ending with 'Repository' should reside in repositories package
const repositoryRule = ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Repository')
.should()
.resideInPackage('repositories');import { ArchRuleDefinition } from 'archunit-ts';
// Classes with @Service decorator should reside in services package
const serviceRule = ArchRuleDefinition.classes()
.that()
.areAnnotatedWith('Service')
.should()
.resideInPackage('services');
// Classes in services package should be annotated with @Service
const serviceAnnotationRule = ArchRuleDefinition.classes()
.that()
.resideInPackage('services')
.should()
.beAnnotatedWith('Service');import { layeredArchitecture } from 'archunit-ts';
const layerRule = layeredArchitecture()
.layer('Controllers')
.definedBy('controllers')
.layer('Services')
.definedBy('services')
.layer('Repositories')
.definedBy('repositories')
.layer('Models')
.definedBy('models')
// Define access rules
.whereLayer('Controllers')
.mayOnlyAccessLayers('Services')
.whereLayer('Services')
.mayOnlyAccessLayers('Repositories', 'Models')
.whereLayer('Repositories')
.mayOnlyAccessLayers('Models')
.whereLayer('Models')
.mayNotAccessLayers('Controllers', 'Services', 'Repositories');
const violations = await archUnit.checkRule('./src', layerRule);import { ArchRuleDefinition } from 'archunit-ts';
// Classes in domain should not depend on infrastructure
const domainRule = ArchRuleDefinition.classes()
.that()
.resideInPackage('domain')
.should()
.notDependOnClassesThat()
.resideInPackage('infrastructure');Control whether violations fail the build (errors) or just warn (warnings):
import { ArchRuleDefinition } from 'archunit-ts';
// ERROR: Will fail the build (default)
const strictRule = ArchRuleDefinition.classes()
.that()
.resideInPackage('services')
.should()
.haveSimpleNameEndingWith('Service');
// WARNING: Won't fail the build, but will show in output
const lenientRule = ArchRuleDefinition.classes()
.that()
.resideInPackage('legacy')
.should()
.haveSimpleNameEndingWith('Service')
.asWarning();
// Progressive enforcement: Start with warnings, promote to errors later
const phase1Rule = someRule.asWarning(); // Phase 1: Team addresses issues
const phase2Rule = someRule.asError(); // Phase 2: Enforce strictlyUse cases:
- Gradual adoption: Mark legacy code violations as warnings
- Soft launches: Introduce new rules as warnings first
- Non-blocking checks: Informational rules that shouldn't fail builds
- Progressive enforcement: Start lenient, get stricter over time
See API Documentation for complete API documentation.
ArchRuleDefinition- Define architecture rulescreateArchUnit()- Create analyzer instancelayeredArchitecture()- Define layered architectureArchUnitTS.assertNoViolations()- Assert no violations
describe('Express API Architecture', () => {
it('should enforce MVC pattern', async () => {
const archUnit = createArchUnit();
const rules = [
// Controllers should only depend on services
ArchRuleDefinition.classes()
.that()
.resideInPackage('controllers')
.should()
.onlyDependOnClassesThat()
.resideInAnyPackage('services', 'models'),
// Services should not depend on controllers
ArchRuleDefinition.classes()
.that()
.resideInPackage('services')
.should()
.notDependOnClassesThat()
.resideInPackage('controllers'),
// Models should not depend on anything
ArchRuleDefinition.classes()
.that()
.resideInPackage('models')
.should()
.notDependOnClassesThat()
.resideInAnyPackage('controllers', 'services'),
];
const violations = await archUnit.checkRules('./src', rules);
expect(violations).toHaveLength(0);
});
});describe('NestJS Architecture', () => {
it('should enforce module boundaries', async () => {
const archUnit = createArchUnit();
const rules = [
// Controllers should be annotated with @Controller
ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Controller')
.should()
.beAnnotatedWith('Controller'),
// Services should be annotated with @Injectable
ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Service')
.should()
.beAnnotatedWith('Injectable'),
// Repositories should reside in database module
ArchRuleDefinition.classes()
.that()
.haveSimpleNameEndingWith('Repository')
.should()
.resideInPackage('database'),
];
const violations = await archUnit.checkRules('./src', rules);
expect(violations).toHaveLength(0);
});
});import { createArchUnit, ArchRuleDefinition, ArchUnitTS } from 'archunit-ts';
describe('Architecture Tests', () => {
let archUnit: ArchUnitTS;
beforeAll(() => {
archUnit = createArchUnit();
});
it('should follow naming conventions', async () => {
const rule = ArchRuleDefinition.classes()
.that()
.resideInPackage('services')
.should()
.haveSimpleNameEndingWith('Service');
const violations = await archUnit.checkRule('./src', rule);
// Assert no violations
ArchUnitTS.assertNoViolations(violations);
});
});import { expect } from 'chai';
import { createArchUnit, ArchRuleDefinition } from 'archunit-ts';
describe('Architecture Tests', () => {
it('should enforce package rules', async () => {
const archUnit = createArchUnit();
const rule = ArchRuleDefinition.classes()
.that()
.areAnnotatedWith('Service')
.should()
.resideInPackage('services');
const violations = await archUnit.checkRule('./src', rule);
expect(violations).to.have.lengthOf(0);
});
});By default, ArchUnit-TS analyzes **/*.ts, **/*.tsx, **/*.js, and **/*.jsx files. You can customize this:
const archUnit = createArchUnit();
const violations = await archUnit.checkRule(
'./src',
rule,
['**/*.ts', '**/*.tsx'] // Only TypeScript files
);Automatically ignored:
node_modules/dist/build/*.d.tsfiles
The CLI tool allows you to run architecture checks from the command line without writing test code.
npx archunit-ts check ./srcnpx archunit-ts check ./src --rules archunit.rules.tsArchUnit-TS can generate reports in multiple formats:
# Generate HTML report
npx archunit-ts check ./src --format html --output reports/architecture.html
# Generate JSON report
npx archunit-ts check ./src --format json --output reports/architecture.json
# Generate JUnit XML report (for CI/CD integration)
npx archunit-ts check ./src --format junit --output reports/architecture.xml
# Generate Markdown report
npx archunit-ts check ./src --format markdown --output reports/architecture.md
# Custom report title
npx archunit-ts check ./src --format html --output report.html --report-title "My Project Architecture"Enable automatic architecture checks when files change:
# Start watch mode
npx archunit-ts watch
# Watch with custom config
npx archunit-ts watch --config custom.config.js
# Watch specific patterns
npx archunit-ts watch --pattern "src/**/*.ts"
# Watch with verbose output
npx archunit-ts watch --verboseWatch mode features:
- Automatic re-checking on file changes
- Debounced execution (300ms default)
- Clear console output with timestamps
- Shows which files changed
- Graceful shutdown with Ctrl+C
- Ignores node_modules, dist, build, and .d.ts files
--rules <path>- Path to rules configuration file--format <format>- Report format: html, json, junit, or markdown--output <path>- Output path for report--report-title <title>- Custom title for the report--graph-type <type>- Graph format: dot or html (for graph command)--graph-title <title>- Custom title for the graph--direction <dir>- Graph direction: LR, TB, RL, or BT (for DOT graphs)--include-interfaces- Include interfaces in dependency graph--width <pixels>- Graph width for HTML output (default: 1200)--height <pixels>- Graph height for HTML output (default: 800)--no-color- Disable colored output--no-context- Disable code context in violations--verbose, -v- Show verbose output--help- Show help--version- Show version
ArchUnit-TS can generate visual dependency graphs to help you understand and analyze your codebase structure.
Generate an interactive, D3.js-powered dependency graph that you can explore in your browser:
# Generate interactive HTML graph
archunit-ts graph --graph-type html --output ./docs/dependencies.html
# With custom options
archunit-ts graph --graph-type html --output ./graph.html \
--graph-title "My Project Dependencies" \
--width 1600 --height 900 \
--include-interfacesFeatures of the HTML graph:
- Interactive exploration - Click and drag nodes, zoom and pan
- Real-time filtering - Filter by node type or violations
- Physics simulation - Adjustable force-directed layout
- Detailed tooltips - Hover to see dependencies and metadata
- Cycle detection - Automatically highlights if cycles are present
- Color-coded nodes - Different colors for classes, interfaces, and violations
Generate DOT files for use with Graphviz to create publication-quality diagrams:
# Generate DOT file
archunit-ts graph --graph-type dot --output ./docs/dependencies.dot
# Convert to PNG using Graphviz (requires graphviz installation)
dot -Tpng ./docs/dependencies.dot -o ./docs/dependencies.png
# Convert to SVG
dot -Tsvg ./docs/dependencies.dot -o ./docs/dependencies.svg
# With custom layout direction
archunit-ts graph --graph-type dot --output ./graph.dot --direction LRDOT graph features:
- Module clustering - Nodes grouped by module/package
- Relationship types - Different styles for inheritance, implementation, and imports
- Metadata labels - Shows decorators and abstract classes
- Violation highlighting - Nodes with violations are highlighted in red
Generate graphs programmatically in your code:
import { createArchUnit } from 'archunit-ts';
const archUnit = createArchUnit();
// Generate interactive HTML graph
await archUnit.generateHtmlGraph('./src', './docs/graph.html', {
graphOptions: {
title: 'My Application Architecture',
width: 1600,
height: 900,
showLegend: true,
enablePhysics: true,
},
builderOptions: {
includeInterfaces: true,
},
});
// Generate DOT graph
await archUnit.generateDotGraph('./src', './docs/graph.dot', {
graphOptions: {
title: 'Dependency Graph',
direction: 'LR',
clusterByModule: true,
useColors: true,
},
});
// Work with the graph data structure
const graph = await archUnit.createDependencyGraph('./src');
const stats = graph.getStats();
console.log(`Nodes: ${stats.nodeCount}, Edges: ${stats.edgeCount}`);
console.log(`Has cycles: ${stats.hasCycles}`);- Onboarding - Help new team members understand the codebase structure
- Architecture reviews - Visualize actual vs intended architecture
- Refactoring planning - Identify highly coupled modules
- Documentation - Auto-generate architecture diagrams
- Cycle detection - Find and eliminate circular dependencies
ArchUnit-TS provides comprehensive reporting capabilities to visualize and share architecture violations.
- HTML - Interactive, styled reports with statistics
- JSON - Machine-readable format for tooling integration
- JUnit XML - CI/CD integration (Jenkins, GitHub Actions, etc.)
- Markdown - Documentation and PR integration
You can also generate reports programmatically:
import { createArchUnit, createReportManager, ReportFormat, ArchRuleDefinition } from 'archunit-ts';
const archUnit = createArchUnit();
const reportManager = createReportManager();
// Define and check rules
const rule = ArchRuleDefinition.classes()
.that()
.resideInPackage('services')
.should()
.haveSimpleNameEndingWith('Service');
const violations = await archUnit.checkRule('./src', rule);
// Generate HTML report
await reportManager.generateReport(violations, {
format: ReportFormat.HTML,
outputPath: 'reports/architecture.html',
title: 'Architecture Report',
includeTimestamp: true,
includeStats: true,
});
// Generate multiple reports at once
await reportManager.generateMultipleReports(
violations,
[ReportFormat.HTML, ReportFormat.JSON, ReportFormat.JUNIT],
'reports/',
{
title: 'Architecture Analysis',
}
);All reports include:
- Metadata: Title, timestamp, total violations
- Statistics: Total files affected, rules checked, pass/fail counts
- Violations: Detailed list grouped by file and rule
- Source Locations: File paths and line numbers for each violation
- Clean, responsive design
- Color-coded statistics
- Violations grouped by file
- Direct links to source code locations
- Success indicators when no violations found
Use JUnit format for seamless CI/CD integration:
# GitHub Actions example
- name: Run Architecture Tests
run: npx archunit-ts check ./src --format junit --output reports/architecture.xml
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: reports/architecture.xml// Jenkins Pipeline example
stage('Architecture Tests') {
steps {
sh 'npx archunit-ts check ./src --format junit --output reports/architecture.xml'
}
post {
always {
junit 'reports/architecture.xml'
}
}
}- Run in CI/CD: Add architecture tests to your CI/CD pipeline
- Test Early: Run architecture tests alongside unit tests
- Start Small: Begin with simple rules and expand gradually
- Document Intent: Use clear, descriptive rule definitions
- Fail Fast: Configure tests to fail on first violation for faster feedback
- Generate Reports: Use reports to communicate violations to your team
Check the /examples directory for complete working examples:
examples/express-api/- Express.js REST APIexamples/nestjs-app/- NestJS applicationexamples/clean-architecture/- Clean architecture example
As codebases grow, they tend to drift from their intended architecture. ArchUnit-TS helps prevent this by making architecture testable:
// ❌ This would fail if a developer accidentally adds a dependency
const rule = ArchRuleDefinition.classes()
.that()
.resideInPackage('domain')
.should()
.notDependOnClassesThat()
.resideInPackage('infrastructure');Architecture tests serve as executable documentation that never gets outdated:
// This test documents that controllers should only use services
describe('Architecture Rules', () => {
it('controllers should only depend on services', async () => {
// Test doubles as documentation
});
});Catch architectural violations in CI/CD before they reach code review:
✓ Architecture Tests
✓ services should reside in services package
✓ controllers should only depend on services
✗ domain should not depend on infrastructure
Violation: UserEntity depends on PostgresClient
Location: src/domain/entities/UserEntity.ts:5- 📖 API Documentation - Complete API reference
- 📚 Documentation - Comprehensive documentation hub
- ❓ FAQ - Frequently asked questions
- 🛣️ Roadmap - Future plans and features
- 📋 Pattern Library - Pre-built architectural patterns
- 📊 Architectural Metrics - Metrics and fitness scoring
- 🔒 Security Policy - Security guidelines
- 📝 Changelog - Release history
- 🤝 Contributing Guide - How to contribute
- 💬 GitHub Discussions - Ask questions and share ideas
- 🐛 Issue Tracker - Report bugs
- 📧 Email: admin@manjericao.io
Contributions are welcome! Please read CONTRIBUTING.md for details.
MIT © Manjericao Team
Inspired by ArchUnit for Java, created by TNG Technology Consulting.