Architectural layer enforcement for TypeScript and JavaScript projects.
Layerguard prevents architectural violations before they happen. Define your layers, declare how dependencies can flow between them, and Layerguard enforces those rules in CI, pre-commit hooks, or your editor.
- Catch violations early - Get instant feedback when code violates your architecture
- Framework-agnostic - Works with Next.js, Vite, Angular, Node backends, and more
- Fast incremental checking - Only re-checks files that changed
- Zero runtime impact - Static analysis only, no runtime dependencies
- Monorepo ready - First-class workspace support for pnpm, npm, and Yarn workspaces
- ESLint integration - Inline editor feedback alongside your other linting rules
- Installation
- Quick Start
- Configuration
- CLI Commands
- Monorepo Support
- Framework Support
- ESLint Integration
- GitHub Actions
- VS Code Extension
- Programmatic API
- License
npm install --save-dev layerguardOr with your preferred package manager:
pnpm add -D layerguard
yarn add -D layerguardRequirements: Node.js 18.0.0 or higher, TypeScript 4.7.0 or higher
Run the interactive setup wizard:
npx layerguard initThe wizard detects your project structure, suggests layers, and generates layerguard.config.ts.
Then validate your architecture:
npx layerguard checkIf code violates the rules:
layerguard check
✗ 1 violation
ERROR Layer violation: repository -> services
src/repository/users.ts:3 -> src/services/auth.ts
Repository cannot import from services.
Create layerguard.config.ts in your project root:
import { defineConfig } from 'layerguard'
export default defineConfig({
layers: {
handlers: { path: 'src/handlers/' },
services: { path: 'src/services/' },
repository: { path: 'src/repository/' },
},
flow: [
'handlers -> services',
'services -> repository',
],
})Each layer represents an architectural tier in your project:
layers: {
// Layer name -> configuration
pages: { path: 'src/pages/' },
components: { path: 'src/components/' },
hooks: { path: 'src/hooks/' },
utils: { path: 'src/utils/' },
}Files within a layer directory belong to that layer. Files outside all layers are "unlayered" and can be handled via the unlayeredImports rule.
Flow rules define allowed dependency directions:
flow: [
'pages -> components', // pages can import from components
'pages -> hooks', // pages can import from hooks
'components -> hooks', // components can import from hooks
'hooks <-> utils', // bidirectional: hooks and utils can import each other
]A -> B- Unidirectional: A can import from B, but B cannot import from AA <-> B- Bidirectional: A and B can import from each other
Any import not explicitly allowed is a violation.
Layers can contain sublayers with their own rules:
layers: {
components: {
path: 'src/components/',
sublayers: {
features: { path: 'src/components/features/', isolated: true },
shared: { path: 'src/components/shared/' },
},
flow: ['features -> shared'],
},
}With isolated: true, sibling directories within the sublayer cannot import from each other. For example, features/auth/ cannot import from features/billing/.
Restrict which files can be imported from outside a layer:
layers: {
services: {
path: 'src/services/',
publicApi: 'index.ts', // Only index.ts can be imported from outside
},
}Other files within the layer become private. This encourages clean module boundaries.
You can specify multiple public files:
publicApi: ['index.ts', 'types.ts']Configure additional architectural checks:
rules: {
// Circular dependency detection
circular: 'error', // 'error' | 'warn' | 'off' (default: 'error')
// Orphan file detection (files not imported anywhere)
orphans: 'warn', // 'error' | 'warn' | 'off' (default: 'off')
// Whether to enforce rules on type-only imports
typeOnlyImports: 'ignore', // 'enforce' | 'ignore' (default: 'ignore')
// Imports from layered files to unlayered files
unlayeredImports: 'warn', // 'error' | 'warn' | 'ignore' (default: 'ignore')
// How to resolve barrel re-exports
barrelResolution: 'import-site', // 'import-site' | 'origin' (default: 'import-site')
// Workspace package import handling
workspaceImports: 'ignore', // 'enforce' | 'ignore' (default: 'ignore')
// Maximum import chain depth (transitive imports)
maxImportDepth: 5, // Warns if exceeded
// Maximum imports per file
maxImportsPerFile: 20, // Warns if exceeded
}Barrel Resolution Modes:
import-site- Check against where the import points (e.g.,services/index.ts)origin- Trace re-exports to their source file (catches hidden violations through barrels)
Document exceptions when violations are intentional:
exceptions: [
{
from: 'src/repository/users.ts',
to: 'src/services/auth.ts',
reason: 'User repository needs auth service for password hashing',
},
{
from: 'src/legacy/**/*.ts',
to: 'src/**/*.ts',
reason: 'Legacy code is being migrated gradually',
},
]Exceptions require a reason - this serves as living documentation for architectural decisions.
Exclude files from analysis:
ignore: [
'**/*.test.ts',
'**/*.spec.ts',
'**/mocks/**',
'**/fixtures/**',
]Specify custom tsconfig path(s):
// Single tsconfig
tsconfig: 'tsconfig.app.json',
// Multiple tsconfigs (merged)
tsconfig: ['tsconfig.app.json', 'tsconfig.server.json'],Validate architecture rules:
layerguard check [options]| Option | Description |
|---|---|
--ci |
Output in GitHub Actions annotation format |
--json |
Output violations as JSON |
--no-color |
Disable colored output |
--type-only |
Enforce rules on type-only imports |
--watch, -w |
Watch mode: re-check on file changes |
--no-cache |
Disable incremental caching |
--package, -p |
Check a specific workspace package |
--all |
Check all workspace packages |
--github-pr-comment |
Post results as a PR comment |
--pr-number |
PR number for comment (auto-detected in CI) |
Examples:
layerguard check # Standard check
layerguard check --ci # GitHub Actions annotations
layerguard check --json # JSON output for tooling
layerguard check --watch # Watch mode
layerguard check --package apps/web # Check specific package
layerguard check --all # Check all packages in monorepoDisplay architecture diagram:
layerguard show [options]| Option | Description |
|---|---|
--ascii |
Use ASCII characters instead of Unicode |
--flow-only |
Show only flow rules, no diagram |
Interactive setup wizard:
layerguard init [options]| Option | Description |
|---|---|
-y, --yes |
Skip prompts and use detected defaults |
Generate HTML or Markdown report:
layerguard report [options]| Option | Description |
|---|---|
--output, -o |
Output file path (default: layerguard-report.html) |
--markdown, --md |
Output as Markdown instead of HTML |
--stdout |
Print to stdout instead of file |
--from |
Load historical data from JSON for trend charts |
--title |
Custom report title |
Examples:
layerguard report # Generate HTML report
layerguard report --markdown # Markdown summary
layerguard report -o reports/arch.html # Custom output path
layerguard report --from history.json # Include trend dataLayerguard detects pnpm, npm, and Yarn workspaces automatically.
Each package can have its own layerguard.config.ts:
my-monorepo/
├── package.json # workspaces: ["packages/*", "apps/*"]
├── packages/
│ └── shared/
│ └── layerguard.config.ts
└── apps/
└── web/
└── layerguard.config.ts
# By name
layerguard check --package @myorg/web
# By path
layerguard check --package apps/weblayerguard check --allThis finds all packages with layerguard.config.ts and checks them in sequence.
By default, workspace package imports (e.g., import { foo } from '@myorg/shared') are treated like external packages. Enable cross-package enforcement with:
rules: {
workspaceImports: 'enforce',
}Set framework to enable framework-specific intelligence:
export default defineConfig({
framework: 'nextjs-app',
// ...
})| Value | Framework |
|---|---|
nextjs-app |
Next.js App Router (page.tsx, layout.tsx, route.ts) |
nextjs-pages |
Next.js Pages Router (_app.tsx, _document.tsx) |
vite-react |
Vite + React (main.tsx, vite.config.ts) |
vite-react-router |
Vite + React Router file conventions |
vite-tanstack-router |
Vite + TanStack Router file conventions |
vue-nuxt |
Nuxt directory structure |
angular |
Angular module conventions |
node-backend |
Node.js backend patterns |
custom |
Manual configuration only |
Framework detection enables:
- Entry point recognition (excluded from orphan detection)
- Framework-specific file patterns
- Better layer suggestions in
layerguard init
Get inline editor feedback with the ESLint plugin:
// eslint.config.js
import layerguard from 'layerguard/eslint'
export default [
...layerguard.configs.recommended,
// your other config...
]The plugin reads your layerguard.config.ts and reports violations as you type.
Add to your CI workflow:
name: Architecture Check
on: [push, pull_request]
jobs:
layerguard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx layerguard check --ciThe --ci flag outputs GitHub Actions annotations, showing violations inline in the PR diff.
Post a summary comment on pull requests:
- run: npx layerguard check --github-pr-comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}This requires the gh CLI (pre-installed on GitHub-hosted runners).
Install the Layerguard extension for:
- Inline diagnostics showing violations as you type
- Quick fixes for common issues
- Hover information showing layer relationships
Use Layerguard programmatically in your tools:
import { loadConfig, validateConfig } from 'layerguard/config'
import { buildDependencyGraph } from 'layerguard/parser'
import { createFlowChecker } from 'layerguard/enforcer'
// Load and validate config
const { config } = await loadConfig(process.cwd())
const validation = validateConfig(config, process.cwd())
if (!validation.valid) {
console.error(validation.errors)
process.exit(1)
}
// Build dependency graph
const graph = buildDependencyGraph({
projectRoot: process.cwd(),
include: ['src/**/*.ts'],
})
// Check for violations
const checker = createFlowChecker(config)
const result = checker.checkGraph(graph)
console.log(`Found ${result.violations.length} violations`)| Export | Description |
|---|---|
layerguard |
Main entry point |
layerguard/config |
Config loading and validation |
layerguard/parser |
Dependency graph building |
layerguard/enforcer |
Violation checking |
layerguard/plugins |
Framework plugin interfaces |
layerguard/eslint |
ESLint plugin |