Skip to content

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:

Terminal window
npm install -D @stricture/eslint-plugin
# or
pnpm add -D @stricture/eslint-plugin
# or
yarn add -D @stricture/eslint-plugin

Step 2: Configure Stricture

You have two options:

Configure everything in your ESLint config:

eslint.config.js
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
]

Option B: Separate Config File

Create .stricture/config.json:

Terminal window
mkdir -p .stricture
.stricture/config.json
{
"preset": "@stricture/hexagonal"
}

Then configure ESLint:

eslint.config.js
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:

Terminal window
mkdir -p src/core/domain
mkdir -p src/adapters/driven/database

Create a domain file:

src/core/domain/user.ts
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:

src/core/domain/order.ts
// ❌ 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:

Terminal window
npx eslint src/core/domain/order.ts

You’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)

src/core/ports/order-repository.ts
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)

src/core/domain/order.ts
// ✅ Domain is now pure - no infrastructure imports
export 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

src/core/application/create-order.ts
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

src/adapters/driven/database/postgres-order-repository.ts
import { Order } from '../../../core/domain/order';
import { OrderRepository } from '../../../core/ports/order-repository';
// ✅ Adapter implements port interface
export 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

src/index.ts
import { CreateOrderUseCase } from './core/application/create-order';
import { PostgresOrderRepository } from './adapters/driven/database/postgres-order-repository';
// Composition root - wire dependencies
const orderRepo = new PostgresOrderRepository(db);
const createOrder = new CreateOrderUseCase(orderRepo);
// Use the use case
const order = await createOrder.execute(items);

Run ESLint again:

Terminal window
npx eslint src/
✨ No errors found!

Perfect! Your architecture is now clean and enforced.

What You’ve Learned

In 5 minutes, you’ve:

  1. Installed Stricture with a preset
  2. Created a config (single line!)
  3. Added ESLint rule to enforce boundaries
  4. Saw a violation caught automatically
  5. 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:

Terminal window
npx stricture init

The CLI will:

  • ✅ Detect your project framework (Next.js, NestJS, standard TypeScript, etc.)
  • ✅ Recommend appropriate presets based on your project structure
  • ✅ Create .stricture/config.json automatically
  • ✅ Update your ESLint configuration
  • ✅ Install required dependencies

This is the fastest way to get started, especially for framework-specific projects like Next.js.

View full CLI documentation →

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:

.stricture/config.json
{
"preset": "@stricture/layered"
}
.stricture/config.json
{
"preset": "@stricture/nextjs"
}

See all available presets.

Customize Rules

Override or extend preset rules:

.stricture/config.json
{
"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:

.github/workflows/ci.yml
- name: Lint
run: npm run lint
- name: Check Architecture
run: npx eslint .
Learn Core Concepts

Understand how Stricture works:

Read the Core Concepts guide.

Common Questions

Can I use multiple presets?

Yes! Combine presets to layer multiple architectural patterns or organizational standards:

.stricture/config.json
{
"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:

.stricture/config.json
{
"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:

eslint.config.js
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