Reference

Conventions

Naming conventions, file organization, and coding standards for Nuxt Crouton

This guide documents the conventions used throughout Nuxt Crouton. Following these conventions ensures consistency, predictability, and easier collaboration.

Naming Conventions

Collection Names

Collections should follow these rules:

  • Use plural names: products, users, orders
  • Use camelCase for multi-word names: blogPosts, orderItems, userProfiles
  • Be descriptive: userProfiles is better than just profiles
  • Avoid abbreviations: categories not cats
  • Avoid generic names: Don't use items, data, records

Examples:

✅ Good: products, blogPosts, orderItems
❌ Bad: product (singular), blog_posts (snake_case), items (too generic), prods (abbreviated)

Field Names

Field names should:

  • Use camelCase: firstName, createdAt, isActive
  • Be descriptive: emailAddress not email if you also have emailVerified
  • Use conventional names:
    • Dates: createdAt, updatedAt, publishedAt, deletedAt
    • Booleans: isActive, hasAccess, canEdit
    • Relationships: userId, categoryId, authorId

Examples:

{
  "firstName": { "type": "string" },        // ✅ Good
  "isPublished": { "type": "boolean" },     // ✅ Good
  "publishedAt": { "type": "date" },        // ✅ Good
  "authorId": { "type": "string", "refTarget": "authors" },  // ✅ Good

  "first_name": { "type": "string" },       // ❌ Bad: snake_case
  "published": { "type": "boolean" },       // ❌ Bad: not clear it's a boolean
  "pub_date": { "type": "date" },          // ❌ Bad: abbreviated
  "author": { "type": "string" }            // ❌ Bad: should be authorId
}

File Names

Generated and custom files follow these patterns:

Component Files:

  • Generated components: _Form.vue, List.vue
  • Custom components: ProductCard.vue, OrderStatusBadge.vue, UserAvatar.vue
  • Use PascalCase for all Vue components

Composable Files:

  • Generated composables: useShopProducts.ts, useShopOrders.ts (layer prefix + collection name)
  • Custom composables: useProductHelpers.ts, useOrderFilters.ts
  • Use camelCase starting with use

Utility Files:

  • Use camelCase: formatters.ts, validators.ts, productHelpers.ts

Type Files:

  • Use camelCase: products.ts, orders.ts, shared.ts

Layer Names

Layer directory names should:

  • Match collection names: If collection is products, layer is layers/products/
  • Use plural, camelCase: layers/blogPosts/, layers/orderItems/
  • Group by domain for multiple collections: layers/shop/ for products, categories, orders

File Organization

Standard Layer Structure

Every generated layer follows this structure:

layers/[layer-name]/collections/[collection-name]/
├── app/
│   ├── components/
│   │   ├── _Form.vue              # Generated form component
│   │   └── List.vue               # Generated list component
│   │
│   └── composables/
│       └── use[LayerCollection].ts  # Generated CRUD composable
│
├── server/
│   ├── api/teams/[id]/[layer]-[collection]/
│   │   ├── index.get.ts           # List endpoint
│   │   ├── index.post.ts          # Create endpoint
│   │   ├── [collectionId].patch.ts # Update endpoint (PATCH)
│   │   └── [collectionId].delete.ts # Delete endpoint
│   │
│   └── database/
│       └── schema.ts              # Drizzle schema
│
├── types.ts                       # TypeScript types
└── nuxt.config.ts                 # Layer config

Custom Components Placement

Place custom components in organized subdirectories:

layers/shop/collections/products/app/components/
├── _Form.vue                    # Generated
├── List.vue                     # Generated
├── fields/                       # Custom field components
│   ├── PriceField.vue
│   ├── StockField.vue
│   └── CategoryField.vue
└── cards/                        # Custom card layouts
    ├── ProductCard.vue
    └── ProductCardMini.vue

Schema Files

Schema files should be organized in a schemas/ directory using JSON format:

schemas/
├── products.json        # Single collection schema
├── categories.json
├── shop/                # Domain-grouped schemas
│   ├── products.json
│   ├── orders.json
│   └── orderItems.json
└── blog/
    ├── posts.json
    ├── authors.json
    └── tags.json

Collection Schema Patterns

Basic Schema Structure

{
  "title": {
    "type": "string",
    "meta": { "required": true, "label": "Product Name" }
  },
  "price": {
    "type": "number",
    "meta": { "required": true }
  },
  "categoryId": {
    "type": "string",
    "refTarget": "categories"
  }
}

Auto-Generated Fields

NEVER define these fields in your schema - they're auto-generated:

  • id - Always generated (UUID or nanoid)
  • teamId - Always generated (team-scoped by default)
  • owner - Always generated (team-scoped by default)
  • createdAt - Generated when useMetadata: true (default)
  • updatedAt - Generated when useMetadata: true (default)
  • createdBy - Generated when useMetadata: true (default)
  • updatedBy - Generated when useMetadata: true (default)

Common Mistake: Defining auto-generated fields in your schema causes duplicate key errors during build.

// ❌ BAD - These are auto-generated!
{
  "id": { "type": "string" },
  "createdAt": { "type": "date" },
  "teamId": { "type": "string" }
}

// ✅ GOOD - Only define your custom fields
{
  "title": { "type": "string" },
  "description": { "type": "text" },
  "price": { "type": "number" }
}

Field Type Conventions

Text Fields:

{ "title": { "type": "string" } }
{ "description": { "type": "text" } }

Number Fields:

{ "quantity": { "type": "number" } }
{ "price": { "type": "decimal", "meta": { "precision": 10, "scale": 2 } } }

Boolean Fields:

{ "isActive": { "type": "boolean", "meta": { "default": true } } }

Date Fields:

{ "publishedAt": { "type": "date" } }

Reference Fields (use string type with refTarget):

{ "categoryId": { "type": "string", "refTarget": "categories" } }

Other Types: json, repeater, array, image, file

Component Conventions

Customization Patterns

Override generated components by creating your own component with the same name in the collection directory. The generated _Form.vue and List.vue can be freely edited since they live in your project.

Props Naming

Component props follow Vue conventions:

  • Use camelCase in script: modelValue, errorMessage, showModal
  • Use kebab-case in templates: model-value, error-message, show-modal
  • Prefix boolean props: isActive, hasError, canEdit

API Endpoint Patterns

RESTful Conventions

Generated API endpoints follow REST conventions:

GET    /api/teams/:teamId/shop-products          # List all
POST   /api/teams/:teamId/shop-products          # Create new
PATCH  /api/teams/:teamId/shop-products/:id      # Update existing
DELETE /api/teams/:teamId/shop-products/:id      # Delete

Query Parameters

Standard query parameters:

  • page - Page number (1-indexed)
  • limit - Items per page
  • search - Search query
  • sort - Sort field (e.g., title, -createdAt for descending)
  • filter - JSON filter object

Example:

GET /api/products?page=2&limit=20&search=laptop&sort=-price

TypeScript Conventions

Type Naming

  • Collection types: PascalCase matching collection: Product, BlogPost, OrderItem
  • Input types: Suffix with Input: ProductInput, CreateProductInput
  • Query types: Suffix with Query: ProductQuery, ProductsQuery
  • Response types: Suffix with Response: ProductResponse, ProductsResponse

Example:

// Collection type
export interface Product {
  id: string
  title: string
  price: number
  createdAt: Date
}

// Input type
export interface CreateProductInput {
  title: string
  price: number
}

// Query type
export interface ProductsQuery {
  page?: number
  limit?: number
  search?: string
}

// Response type
export interface ProductsResponse {
  items: Product[]
  total: number
  page: number
}

Import Conventions

// ✅ Good - Organized imports
import { ref, computed } from 'vue'
import type { Product } from '~/layers/shop/collections/products/types'
import { useProducts } from '~/layers/shop/collections/products/app/composables/useShopProducts'

// ❌ Bad - Mixed order, no type imports
import { useProducts } from '~/layers/shop/collections/products/app/composables/useShopProducts'
import { ref, computed } from 'vue'
import { Product } from '~/layers/shop/collections/products/types' // Should be type import

Code Style

Vue Component Structure

Components should follow this order:

<!-- 1. Script setup -->
<script setup lang="ts">
// 1. Imports
import { ref, computed } from 'vue'
import type { Product } from '~/types'

// 2. Props
interface Props {
  product: Product
}
const props = defineProps<Props>()

// 3. Emits
const emit = defineEmits<{
  'update': [product: Product]
}>()

// 4. Composables
const { mutate } = useCroutonMutate()

// 5. Reactive state
const isEditing = ref(false)

// 6. Computed
const displayPrice = computed(() => `$${props.product.price.toFixed(2)}`)

// 7. Methods
const handleSave = async () => {
  // ...
}
</script>

<!-- 2. Template -->
<template>
  <!-- Component markup -->
</template>

<!-- 3. Styles (if needed) -->
<style scoped>
/* Component styles */
</style>

Composable Structure

// layers/products/composables/useProducts.ts

import { ref } from 'vue'
import type { Product } from '~/types'

export const useProducts = () => {
  // 1. State
  const products = ref<Product[]>([])
  const loading = ref(false)

  // 2. Methods
  const fetchProducts = async () => {
    loading.value = true
    try {
      products.value = await $fetch('/api/products')
    } finally {
      loading.value = false
    }
  }

  // 3. Return public API
  return {
    products: readonly(products),
    loading: readonly(loading),
    fetchProducts
  }
}

Best Practices Summary

  1. Follow naming conventions: Plural collections, camelCase fields, PascalCase components
  2. Organize files consistently: Use standard layer structure
  3. Never define auto-generated fields: Let Nuxt Crouton add id, createdAt, etc.
  4. Use TypeScript: Type everything for safety and better DX
  5. Follow Vue conventions: Component structure, prop naming, import order
  6. Keep customizations separate: Custom components in subdirectories
  7. Use descriptive names: Avoid abbreviations and generic names