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.
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.
Create a table to store email addresses.
// server/db/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(),
})
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:
publicsubscribers['create']{ "email": "..." }.403 Forbidden (protecting your list).This recipe demonstrates a "User-Generated Content" system where the public submits content, but it requires Admin approval before being displayed on the site.
We include a status field to manage visibility.
// server/db/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'),
})
publictestimonials['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.
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 '../db/schema'
import { db } from 'hub:db'
export default defineEventHandler(async () => {
// Fetch only 'active' testimonials
return await db.select()
.from(testimonials)
.where(eq(testimonials.status, 'active'))
.orderBy(desc(testimonials.createdAt))
.limit(9)
.all()
})
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.
This recipe demonstrates how to allow users to select their role during registration (e.g., "Buyer" vs "Seller").
First, ensure you have the roles defined in your roles table (e.g., "buyer", "seller").
Update your signup form to include a role selection dropdown.
// app/pages/signup.vue
// 1. Fetch available roles
const { data: roles } = await useFetch<any[]>('/api/roles')
// 2. Filter internal roles and format for the dropdown
const selectableRoles = computed(() => {
return roles.value
?.filter((r: any) => !['public', 'user', 'manager', 'admin'].includes(r.name))
.map((r: any) => ({
label: r.name ? r.name.charAt(0).toUpperCase() + r.name.slice(1) : 'Unknown',
value: r.id
})) || []
})
// 3. Add to Form Fields
const fields = computed(() => [
// ... name, email, password
{
name: "roleId",
type: "select",
label: "Register As",
items: selectableRoles.value,
}
])
// 4. Handle Submission
// Nuxt UI selects might return an object instead of a value.
// Use a helper to extract the numeric ID.
async function onSubmit(payload: any) {
const data = { ...payload.data };
if (data.roleId && typeof data.roleId === 'object') {
data.roleId = data.roleId.value;
}
await $fetch("/api/auth/signup", {
method: "POST",
body: data,
});
// ...
}
The system's default signup endpoint (/api/auth/signup) accepts an optional roleId.