Real-World Recipes

Practical guides for implementing common features using Nuxt Auto CRUD.

This section provides complete "recipes" for common application features. These examples demonstrate how to combine schemas, permissions, and custom logic to build real-world functionality.

Recipe 1: Newsletter Subscription (Write-Only)

This recipe demonstrates how to create a simple "Subscribe" form where public users can add their email address, but cannot see who else has subscribed.

1. Define the Schema

Create a table to store email addresses.

// server/database/schema/subscribers.ts
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { systemFields } from './utils'

export const subscribers = sqliteTable('subscribers', {
  ...systemFields,
  email: text('email').notNull().unique(),
})

2. Configure Permissions

We want Public users to be able to Create (subscribe), but NOT List or Read (privacy). Admins or Managers can have full access.

Database Seed / Permission Setup:

  • Role: public
  • Resource: subscribers
  • Permissions: ['create']

3. The Result

  • POST /api/subscribers: Public users can submit { "email": "..." }.
  • GET /api/subscribers: Public users will receive 403 Forbidden (protecting your list).
  • Admin Dashboard: Admins can view and manage the full list of subscribers.

Recipe 2: Testimonials System (Approval Workflow)

This recipe demonstrates a "User-Generated Content" system where the public submits content, but it requires Admin approval before being displayed on the site.

1. Define the Schema

We include a status field to manage visibility.

// server/database/schema/testimonials.ts
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { systemFields } from './utils'

export const testimonials = sqliteTable('testimonials', {
  ...systemFields,
  // Default to 'inactive' so new submissions aren't immediately visible
  status: text('status', { enum: ['active', 'inactive'] }).default('inactive'),
  name: text('name').notNull(),
  role: text('role').notNull(),
  content: text('content').notNull(),
  avatar: text('avatar'),
  company: text('company'),
})

2. Configure Permissions

  • Role: public
  • Resource: testimonials
  • Permissions: ['create'] (and optionally ['read', 'list'] if you want them to see all testimonials, otherwise restrict this).

In our template, we allow ['create', 'read', 'list'] for simplicity, but we use a Custom Endpoint to serve only "Approved" testimonials to the landing page.

3. Custom Endpoint for "Active" Items

Since the generic /api/testimonials endpoint might be restricted or return all items (including inactive ones if public has list access), we create a specific endpoint for the frontend display.

// server/api/active-testimonials.get.ts
import { eq, desc } from 'drizzle-orm'
import { testimonials } from '../database/schema'
import { useDrizzle } from '../utils/drizzle'

export default defineEventHandler(async () => {
  const db = useDrizzle()
  
  // Fetch only 'active' testimonials
  return await db.select()
    .from(testimonials)
    .where(eq(testimonials.status, 'active'))
    .orderBy(desc(testimonials.createdAt))
    .limit(9)
    .all()
})

4. Frontend Integration

On your landing page, fetch from the custom endpoint:

<script setup lang="ts">
const { data: testimonials } = await useFetch('/api/active-testimonials')
</script>

And use the Auto CRUD Form for submissions:

<CrudCreateRow
  resource="testimonials"
  :schema="{
    resource: 'testimonials',
    fields: [
      { name: 'name', type: 'text', required: true },
      { name: 'role', type: 'text', required: true },
      { name: 'content', type: 'textarea', required: true }
    ]
  }"
/>

The CrudCreateRow component automatically handles the POST request to /api/testimonials.