Patterns

Working with Relations

Learn how to connect data between collections using foreign keys and Drizzle relations

Relations connect data between collections, like linking products to categories or posts to authors. Crouton stores relations as foreign key IDs only - it does not embed full related objects. Most apps can start simple by storing these IDs and querying manually when needed:

// Store the relationship
const product = {
  id: '123',
  name: 'Widget',
  categoryId: 'cat-456'  // ← Just store the ID
}

// Query when needed
const category = await db.select()
  .from(categories)
  .where(eq(categories.id, product.categoryId))

This approach works well when you:

  • Have simple CRUD apps
  • Only need relations occasionally
  • Prefer explicit queries
  • Are learning and prototyping

It's the recommended starting point for most applications.

Reference Fields with refTarget

The generator automatically creates UI components for reference fields when you use the refTarget property in your schema:

{
  "authorId": {
    "type": "uuid",
    "refTarget": "authors",
    "meta": {
      "required": true,
      "label": "Author"
    }
  }
}

Cross-Layer References

By default, references are scoped to the current layer. To reference collections outside your layer (like a shared users table), prefix with a colon:

{
  "updatedBy": {
    "type": "string",
    "refTarget": ":users",
    "meta": {
      "label": "Updated By"
    }
  }
}

This generates collection="users" instead of collection="shopUsers", allowing references to global or external collections.

Automatic Form Component

The generator creates a ReferenceSelect component that provides:

  • Searchable dropdown - Shows all items from the referenced collection
  • Create button - Quick creation of new related items
  • Auto-selection - Newly created items are automatically selected
<!-- Generated automatically in Form.vue -->
<UFormField label="Author" name="authorId">
  <ReferenceSelect
    v-model="state.authorId"
    collection="authors"
    label="Author"
  />
</UFormField>

Automatic List Component

In table views, reference fields display as CardMini components showing the referenced item's title with a quick-edit button:

<!-- Generated automatically in List.vue -->
<template #authorId-cell="{ row }">
  <CardMini
    v-if="row.original.authorId"
    :id="row.original.authorId"
    collection="authors"
  />
</template>

User Experience Flow

  1. User opens "Create Post" form
  2. Sees "Author" field with searchable dropdown
  3. Can select existing author OR click "+" to create new
  4. New author modal opens
  5. After saving, new author is automatically selected
  6. User continues with post creation

This provides a seamless experience for managing related data without manual coding.

Advanced: Drizzle Relations

For apps with lots of related data queries, Drizzle relations let you fetch related data in one query, avoiding N+1 query problems:

// Define relation (one-time setup in schema.ts)
import { relations } from 'drizzle-orm'

export const productsRelations = relations(products, ({ one }) => ({
  category: one(categories, {
    fields: [products.categoryId],
    references: [categories.id]
  })
}))

// Query with automatic join
const product = await db.query.products.findFirst({
  where: eq(products.id, '123'),
  with: { category: true }  // ← Drizzle handles the join
})

// Now product.category is populated in ONE query
console.log(product.category.name)

When to use this:

  • ✅ Fetching lists with related data (100 products + their categories = 1 query, not 101)
  • ✅ Nested data (Product → Category → ParentCategory)
  • ✅ Complex filtering ("Get products WHERE category.name = 'Electronics'")
  • ✅ Performance critical queries

Best Practices

✅ DO:

  • Start simple - Store foreign keys, query manually when needed
  • Add relations later - Only if you have performance problems or N+1 queries
  • Use useCollectionQuery to fetch related collections for dropdowns
  • Add database indexes on foreign key columns for performance
  • Document your relation patterns in code comments

❌ DON'T:

  • Over-engineer with Drizzle relations unless you need them
  • Forget to handle null/missing relations (category?.name || 'N/A')
  • Mix manual joins and Drizzle relations in the same query (pick one approach)
  • Skip validation on foreign keys (ensure referenced item exists)