Fundamentals

Architecture

Understanding Nuxt Crouton's two-layer architecture and domain-driven design approach

Nuxt Crouton uses a two-layer architecture that separates generated code from the stable core library. This approach, combined with Nuxt's layer system, allows you to organize your code by business domain while maintaining flexibility and control.

The Two-Layer Architecture

The core principle behind Nuxt Crouton is the separation between your customizable generated code and the stable core library. This architecture gives you the freedom to modify generated files while benefiting from updates to the underlying framework.

┌─────────────────────────────────────┐
│  Generated Code (Yours)             │
│  - Forms, Lists, Tables             │
│  - You customize freely             │
│  - Lives in YOUR project            │
└─────────────────────────────────────┘
            ↓ uses
┌─────────────────────────────────────┐
│  Core Library (Stable)              │
│  - Composables, utilities           │
│  - Modal management, caching        │
│  - Updates via npm                  │
└─────────────────────────────────────┘

Generated Code Layer

The generated code layer includes all the CRUD interfaces you create using the Crouton generator:

  • Forms - Create and edit forms with validation
  • Lists - Collection list views with filtering
  • Tables - Data table components with sorting and actions
  • Composables - Data fetching and mutation logic
  • TypeScript Types - Fully typed interfaces for your collections

This code lives in your project and is yours to customize however you need. You can modify components, add new features, change styling, or completely refactor the generated code—it's all under your control.

Core Library Layer

The core library provides the foundational utilities and composables that your generated code uses:

  • Data Operations - useCollectionQuery(), useCollectionMutation(), useCroutonMutate()
  • Modal Management - useCroutonModal(), useCroutonSlideover(), useCroutonDrawer()
  • Notifications - useCroutonToast() for user feedback
  • Caching - Automatic cache invalidation built on Nuxt's useFetch
  • Utilities - Helper functions for common tasks

The core library stays consistent and receives updates via npm. When you update the nuxt-crouton package, you get bug fixes and new features in the composables without affecting your customized code.

Why This Separation Works

This architecture solves a common problem in code generation tools: how to provide updates without breaking customizations.

With Nuxt Crouton:

  1. You own the generated code - No magic, no hidden abstractions. The generated files are just Vue components and TypeScript code you can read, understand, and modify.
  2. Core stays stable - The composables and utilities follow semantic versioning. Breaking changes are rare and clearly documented.
  3. Customizations persist - Your modifications to generated code won't be overwritten by updates to the core library.
  4. You can regenerate - If you want fresh templates, you can regenerate collections. Just back up your customizations first.

Domain-Driven Design with Nuxt Layers

Nuxt layers allow you to organize your collections by business domain rather than technical function. This creates clear boundaries, improves maintainability, and enables domain-specific deployments.

Why Use Layers?

Clear Boundaries - Each domain is isolated with its own components, composables, and API routes. Changes in one domain don't affect others.

Easier Maintenance - Related code stays together. When working on e-commerce features, everything you need is in the shop layer.

Independent Deployment - Deploy layers separately or together. This is particularly useful for larger applications or multi-tenant systems.

Reusability - Share layers across projects. Build a blog layer once, use it in multiple applications.

Layer Structure

A typical Nuxt Crouton project using layers looks like this:

layers/
  ├── shop/              # E-commerce domain
  │   ├── components/
  │   │   ├── products/
  │   │   │   ├── List.vue
  │   │   │   ├── Form.vue
  │   │   │   └── Table.vue
  │   │   ├── orders/
  │   │   └── inventory/
  │   ├── composables/
  │   │   ├── useProducts.ts
  │   │   ├── useOrders.ts
  │   │   └── useInventory.ts
  │   ├── server/
  │   │   └── api/
  │   │       └── teams/
  │   │           └── [team]/
  │   │               ├── products/
  │   │               ├── orders/
  │   │               └── inventory/
  │   └── types/
  │       ├── products.ts
  │       ├── orders.ts
  │       └── inventory.ts
  │
  ├── blog/              # Content domain
  │   ├── components/
  │   │   ├── posts/
  │   │   ├── authors/
  │   │   └── comments/
  │   ├── composables/
  │   ├── server/
  │   └── types/
  │
  └── admin/             # Admin domain
      ├── components/
      │   ├── users/
      │   ├── roles/
      │   └── permissions/
      ├── composables/
      ├── server/
      └── types/

When to Use Layers

Use layers when you have:

  • Multiple business domains (shop, blog, admin)
  • Large applications with clear domain boundaries
  • Reusable functionality to share across projects
  • Teams working on different domains

Don't use layers for:

  • Simple applications with only a few collections
  • Tightly coupled features that share lots of code
  • Prototypes or early-stage projects

Start simple—you can always add layers later as your application grows.

Generating into Layers

To generate collections into a specific layer, use the --layer flag:

# Generate into the shop layer
npx crouton-generate shop products --fields-file products-schema.json

# Generate into the blog layer
npx crouton-generate blog posts --fields-file posts-schema.json

# Generate into the admin layer
npx crouton-generate admin users --fields-file users-schema.json

If you don't specify a layer, Crouton generates into your main application directory.

Layer Configuration

Each layer can have its own nuxt.config.ts file for layer-specific configuration:

// layers/shop/nuxt.config.ts
export default defineNuxtConfig({
  // Layer-specific configuration
  components: {
    dirs: [
      { path: '~/components', prefix: 'Shop' }
    ]
  }
})

This allows you to:

  • Set up layer-specific component prefixes
  • Configure layer-specific API routes
  • Add layer-specific modules or plugins
  • Override settings for specific domains

Best Practices

Organize by Domain, Not Technical Function

Good - Organized by business domain:

layers/
  ├── shop/           # All e-commerce features
  │   ├── products/
  │   ├── orders/
  │   └── inventory/
  └── blog/           # All content features
      ├── posts/
      ├── authors/
      └── comments/

Bad - Organized by technical function:

components/
  ├── forms/         # All forms together
  ├── lists/         # All lists together
  └── tables/        # All tables together

Everything related to a domain should live in that domain's layer:

  • Components
  • Composables
  • API routes
  • TypeScript types
  • Utilities
  • Tests

This makes it easy to find code, understand dependencies, and make changes without affecting other domains.

Use Clear Naming Conventions

  • Layer names: shop, blog, admin (lowercase, singular or plural based on domain)
  • Collection names: products, orders, posts (plural)
  • Component names: ProductList.vue, OrderForm.vue (PascalCase)
  • Composable names: useProducts, useOrders (camelCase, plural)

Document Layer Dependencies

If one layer depends on another, document it clearly:

// layers/shop/README.md
# Shop Layer

E-commerce functionality for products, orders, and inventory.

## Dependencies
- Requires `admin` layer for user management
- Uses shared types from `core` layer