Quick Start
Get Stricture enforcing architecture boundaries in your project in under 5 minutes.
Goal
By the end of this guide, you’ll have:
- ✅ Stricture installed and configured
- ✅ A working hexagonal architecture preset
- ✅ ESLint reporting architecture violations
- ✅ Your first architectural boundary enforced
Step 1: Install Package
Install the ESLint plugin:
npm install -D @stricture/eslint-plugin# orpnpm add -D @stricture/eslint-plugin# oryarn add -D @stricture/eslint-pluginStep 2: Configure Stricture
You have two options:
Option A: Inline Configuration (Recommended)
Configure everything in your ESLint config:
import stricture from '@stricture/eslint-plugin'
export default [ { // Your existing ESLint config files: ['**/*.ts', '**/*.tsx'], plugins: { /* your other plugins */ }, rules: { /* your other rules */ } }, stricture.configs.hexagonal() // Add Stricture]const stricture = require('@stricture/eslint-plugin')
module.exports = { extends: ['plugin:@stricture/recommended'], rules: { '@stricture/enforce-boundaries': ['error', { inlineConfig: stricture.hexagonalPreset }] }}Option B: Separate Config File
Create .stricture/config.json:
mkdir -p .stricture{ "preset": "@stricture/hexagonal"}Then configure ESLint:
import stricture from '@stricture/eslint-plugin'
export default [ { // Your existing ESLint config files: ['**/*.ts', '**/*.tsx'], plugins: { /* your other plugins */ }, rules: { /* your other rules */ } }, stricture.configs.recommended() // Reads .stricture/config.json]Which Option Should You Use?
- Option A (Inline): Simple setups, using a preset with minimal customization
- Option B (File): Complex configs with many custom boundaries/rules
Step 3: See It In Action
Create a sample directory structure:
mkdir -p src/core/domainmkdir -p src/adapters/driven/databaseCreate a domain file:
export class User { constructor( public readonly id: string, public readonly email: string ) {}
isValid(): boolean { return this.email.includes('@'); }}Now create a violation - domain importing from infrastructure:
// ❌ This violates hexagonal architecture!import { Database } from '../../adapters/driven/database/db';
export class Order { async save() { // Domain should NOT directly use infrastructure await Database.query('INSERT INTO orders...'); }}Run ESLint:
npx eslint src/core/domain/order.tsYou’ll see this error:
src/core/domain/order.ts 2:1 error Import from 'driven-adapters' to 'domain' is not allowed Domain layer must remain pure - no dependencies on other layers or external libraries @stricture/enforce-boundaries✅ Success! Stricture caught the architecture violation.
Fixing the Violation
The correct hexagonal architecture approach:
1. Define a Port (Interface)
import { Order } from '../domain/order';
export interface OrderRepository { save(order: Order): Promise<void>; findById(id: string): Promise<Order | null>;}2. Update Domain (Pure Business Logic)
// ✅ Domain is now pure - no infrastructure importsexport class Order { constructor( public readonly id: string, public readonly items: OrderItem[], public readonly total: number ) {}
addItem(item: OrderItem): Order { return new Order( this.id, [...this.items, item], this.total + item.price ); }}3. Create Application Use Case
import { Order } from '../domain/order';import { OrderRepository } from '../ports/order-repository';
export class CreateOrderUseCase { constructor(private orderRepo: OrderRepository) {}
async execute(items: OrderItem[]): Promise<Order> { const order = new Order( generateId(), items, items.reduce((sum, item) => sum + item.price, 0) );
await this.orderRepo.save(order); return order; }}4. Implement Driven Adapter
import { Order } from '../../../core/domain/order';import { OrderRepository } from '../../../core/ports/order-repository';
// ✅ Adapter implements port interfaceexport class PostgresOrderRepository implements OrderRepository { async save(order: Order): Promise<void> { await this.db.query('INSERT INTO orders...', [ order.id, order.items, order.total ]); }
async findById(id: string): Promise<Order | null> { const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id]); return row ? new Order(row.id, row.items, row.total) : null; }}5. Wire Everything Together
import { CreateOrderUseCase } from './core/application/create-order';import { PostgresOrderRepository } from './adapters/driven/database/postgres-order-repository';
// Composition root - wire dependenciesconst orderRepo = new PostgresOrderRepository(db);const createOrder = new CreateOrderUseCase(orderRepo);
// Use the use caseconst order = await createOrder.execute(items);Run ESLint again:
npx eslint src/✨ No errors found!✅ Perfect! Your architecture is now clean and enforced.
What You’ve Learned
In 5 minutes, you’ve:
- ✅ Installed Stricture with a preset
- ✅ Created a config (single line!)
- ✅ Added ESLint rule to enforce boundaries
- ✅ Saw a violation caught automatically
- ✅ Fixed the architecture using ports and adapters
Even Easier: Use the CLI
Instead of manual setup, you can use the interactive CLI to set everything up automatically:
npx stricture initThe CLI will:
- ✅ Detect your project framework (Next.js, NestJS, standard TypeScript, etc.)
- ✅ Recommend appropriate presets based on your project structure
- ✅ Create
.stricture/config.jsonautomatically - ✅ Update your ESLint configuration
- ✅ Install required dependencies
This is the fastest way to get started, especially for framework-specific projects like Next.js.
Understanding the Flow
Here’s what just happened:
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#b3d9ff','primaryBorderColor':'#66b3ff','secondaryColor':'#ffffb3','secondaryBorderColor':'#ffeb66'}}}%%
graph TB
subgraph "Before (Violation)"
D1[Domain: order.ts]
A1[Adapter: database]
D1 -->|"❌ Direct import"| A1
end
subgraph "After (Clean)"
D2[Domain: order.ts]
P[Port: OrderRepository interface]
U[Use Case: CreateOrderUseCase]
A2[Adapter: PostgresOrderRepository]
U -->|"Uses"| D2
U -->|"Calls through"| P
A2 -->|"Implements"| P
end
style D1 fill:#ffb3b3
style D2 fill:#b3ffb3
Next Steps
Explore Other Presets
Try different architecture patterns:
{ "preset": "@stricture/layered"}{ "preset": "@stricture/nextjs"}See all available presets.
Customize Rules
Override or extend preset rules:
{ "preset": "@stricture/hexagonal", "rules": [ { "id": "custom-rule", "name": "Allow Shared Utils", "severity": "error", "from": { "pattern": "**" }, "to": { "pattern": "src/shared/utils/**" }, "allowed": true } ]}Add to CI/CD
Enforce architecture in your pipeline:
- name: Lint run: npm run lint
- name: Check Architecture run: npx eslint .Common Questions
Can I use multiple presets?
Yes! Combine presets to layer multiple architectural patterns or organizational standards:
{ "preset": "@stricture/hexagonal", "extends": ["@stricture/nextjs"]}Boundaries and rules from all presets are merged together (base preset → extended presets → your custom config).
Does this work with JavaScript?
Yes! Stricture works with both JavaScript and TypeScript. It enforces boundaries by analyzing import statements and file paths, not type information.
How do I ignore certain files?
Use ignorePatterns in your config:
{ "preset": "@stricture/hexagonal", "ignorePatterns": [ "**/*.test.ts", "**/*.spec.ts", "**/test/**" ]}Can I use this with existing code?
Yes! Set the ESLint rule to "warn" instead of "error" to see violations without breaking builds:
import stricture from '@stricture/eslint-plugin'
export default [ { plugins: { '@stricture': stricture }, rules: { '@stricture/enforce-boundaries': ['warn', { inlineConfig: stricture.hexagonalPreset }] } }]Once violations are fixed, change it to 'error' to prevent new violations.
Get Help
- 📖 Core Concepts - Deep dive into how Stricture works
- 🎯 Preset Docs - Explore all architecture presets
- 💬 GitHub Discussions - Ask questions
- 🐛 Report Issues - Found a bug?