Conventions
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:
userProfilesis better than justprofiles - Avoid abbreviations:
categoriesnotcats - Avoid generic names: Don't use
items,data,records
Examples:
# ✅ Good
name: products
name: blogPosts
name: orderItems
# ❌ Bad
name: product # Singular
name: blog_posts # snake_case
name: items # Too generic
name: prods # Abbreviated
Field Names
Field names should:
- Use camelCase:
firstName,createdAt,isActive - Be descriptive:
emailAddressnotemailif you also haveemailVerified - Use conventional names:
- Dates:
createdAt,updatedAt,publishedAt,deletedAt - Booleans:
isActive,hasAccess,canEdit - Relationships:
userId,categoryId,authorId
- Dates:
Examples:
{
"firstName": { "type": "string" }, // ✅ Good
"isPublished": { "type": "boolean" }, // ✅ Good
"publishedAt": { "type": "date" }, // ✅ Good
"authorId": { "type": "reference" }, // ✅ 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": "reference" } // ❌ Bad: should be authorId
}
File Names
Generated and custom files follow these patterns:
Component Files:
- Generated components:
CroutonForm.vue,CroutonTable.vue,CroutonModal.vue - Custom components:
ProductCard.vue,OrderStatusBadge.vue,UserAvatar.vue - Use PascalCase for all Vue components
Composable Files:
- Generated composables:
useProducts.ts,useOrders.ts - 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 islayers/products/ - Use plural, camelCase:
layers/blogPosts/,layers/orderItems/ - Group by domain for multiple collections:
layers/shop/forproducts,categories,orders
File Organization
Standard Layer Structure
Every generated layer follows this structure:
layers/[collection-name]/
├── components/
│ ├── CroutonForm.vue # Generated form component
│ ├── CroutonTable.vue # Generated table component
│ ├── CroutonModal.vue # Generated modal component
│ └── custom/ # Your custom components
│ ├── CustomField.vue
│ └── CustomColumn.vue
│
├── composables/
│ ├── use[Collection].ts # Generated CRUD composable
│ ├── use[Collection]Form.ts # Generated form composable
│ ├── use[Collection]Table.ts # Generated table composable
│ └── use[Collection]Helpers.ts # Your custom composable
│
├── server/
│ ├── api/
│ │ └── [collection]/
│ │ ├── index.get.ts # List endpoint
│ │ ├── index.post.ts # Create endpoint
│ │ ├── [id].get.ts # Get single item
│ │ ├── [id].put.ts # Update endpoint
│ │ └── [id].delete.ts # Delete endpoint
│ │
│ └── database/
│ └── schema/
│ └── [collection].ts # Drizzle schema
│
├── types/
│ └── [collection].ts # TypeScript types
│
├── utils/
│ └── [collection]Helpers.ts # Utility functions
│
├── pages/ (optional)
│ └── [collection]/
│ ├── index.vue # List page
│ └── [id].vue # Detail page
│
├── nuxt.config.ts # Layer config
└── package.json # Layer dependencies
Custom Components Placement
Place custom components in organized subdirectories:
layers/products/components/
├── CroutonForm.vue # Generated
├── CroutonTable.vue # Generated
├── fields/ # Custom field components
│ ├── PriceField.vue
│ ├── StockField.vue
│ └── CategoryField.vue
├── columns/ # Custom column renderers
│ ├── PriceColumn.vue
│ └── StatusColumn.vue
└── cards/ # Custom card layouts
├── ProductCard.vue
└── ProductCardMini.vue
Schema Files
Schema files should be organized in the collections/ directory:
collections/
├── products.yml # Single collection schema
├── categories.yml
├── shop/ # Domain-grouped schemas
│ ├── products.yml
│ ├── orders.yml
│ └── orderItems.yml
└── blog/
├── posts.yml
├── authors.yml
└── tags.yml
Collection Schema Patterns
Basic Schema Structure
name: products # Collection name (plural, camelCase)
description: Product catalog # Human-readable description
icon: i-heroicons-shopping-bag # Heroicon identifier
fields:
- name: title # Field name (camelCase)
type: text # Field type
required: true # Validation
label: Product Name # Display label
- name: price
type: number
validation:
min: 0
max: 999999
- name: categoryId
type: reference
ref-target: categories # Target collection
Auto-Generated Fields
NEVER define these fields in your schema - they're auto-generated:
id- Always generated (UUID or nanoid)createdAt- Generated whenuseMetadata: true(default)updatedAt- Generated whenuseMetadata: true(default)updatedBy- Generated whenuseMetadata: true(default)teamId- Generated whenuseTeamUtility: trueuserId- Generated whenuseTeamUtility: true
Common Mistake: Defining auto-generated fields in your schema causes duplicate key errors during build.
# ❌ BAD - These are auto-generated!
fields:
- name: id
- name: createdAt
- name: teamId
# ✅ GOOD - Only define your custom fields
fields:
- name: title
- name: description
- name: price
Field Type Conventions
Text Fields:
- name: title
type: text # Short text (< 255 chars)
- name: description
type: longtext # Long text (> 255 chars)
- name: content
type: richtext # Rich text with formatting
Number Fields:
- name: quantity
type: integer # Whole numbers
- name: price
type: decimal # Decimal numbers (2 decimal places)
validation:
min: 0
Boolean Fields:
- name: isActive
type: boolean
default: true
Date Fields:
- name: publishedAt
type: date # Date only
- name: eventAt
type: datetime # Date + time
- name: createdAt
type: timestamp # Unix timestamp
Reference Fields:
- name: categoryId
type: reference
ref-target: categories # Target collection (plural)
- name: tagIds
type: reference
ref-target: tags
multiple: true # Many-to-many
Select Fields:
- name: status
type: select
options:
- draft
- published
- archived
default: draft
Component Conventions
Slot Naming
Custom slots follow these patterns:
Form Field Slots:
<template #field-[fieldName]="{ modelValue, updateModelValue, error }">
<CustomField :model-value="modelValue" @update:model-value="updateModelValue" />
</template>
Table Column Slots:
<template #column-[fieldName]="{ row, value }">
<CustomColumn :row="row" :value="value" />
</template>
Action Slots:
<template #actions="{ item }">
<UButton @click="customAction(item)">Custom</UButton>
</template>
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/products # List all
GET /api/products/:id # Get single
POST /api/products # Create new
PUT /api/products/:id # Update existing
DELETE /api/products/:id # Delete
Query Parameters
Standard query parameters:
page- Page number (1-indexed)limit- Items per pagesearch- Search querysort- Sort field (e.g.,title,-createdAtfor 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/products/types/products'
import { useProducts } from '~/layers/products/composables/useProducts'
// ❌ Bad - Mixed order, no type imports
import { useProducts } from '~/layers/products/composables/useProducts'
import { ref, computed } from 'vue'
import { Product } from '~/layers/products/types/products' // 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
- Follow naming conventions: Plural collections, camelCase fields, PascalCase components
- Organize files consistently: Use standard layer structure
- Never define auto-generated fields: Let Nuxt Crouton add
id,createdAt, etc. - Use TypeScript: Type everything for safety and better DX
- Follow Vue conventions: Component structure, prop naming, import order
- Keep customizations separate: Custom components in subdirectories
- Use descriptive names: Avoid abbreviations and generic names
Related Resources
- Best Practices - General best practices
- Schema Format - Detailed schema reference
- Architecture - Understanding layers
- TypeScript Types - Type definitions reference