This boilerplate uses Prisma as the ORM (Object-Relational Mapping) layer for PostgreSQL, providing type-safe database access with excellent developer experience.
The database setup includes:
The complete Prisma schema is located in prisma/schema.prisma. The template includes a comprehensive data model for building production-ready applications.
Authentication (better-auth schema)
User - User accounts with email verificationSession - Active user sessions with token managementAccount - OAuth provider accounts and credentialsVerification - Email and password reset verification codesThese tables are auto-generated by better-auth and follow their standard schema structure.
Payments & monetization (Stripe-ready)
Payment - Transaction records with processor integrationSubscription - Recurring billing with plan managementPrice - Product pricing tiersProduct - Product catalogWebhookEvent - Reliable webhook processing with idempotencyApplication features
Chat & Message - Multi-user chat/messaging systemFile - S3 file storage with user ownershipUserData - Extended user profiles and preferencesUser feedback
BugReport - Bug tracking from usersFeatureRequest - Feature suggestion collectionHere are the core models that power the template. All custom tables use database-generated UUIDs (@db.Uuid) for primary keys. Auth tables use String IDs as required by better-auth.
model User {
id String @id
name String
email String
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
sessions Session[]
accounts Account[]
userData UserData?
payments Payment[]
subscriptions Subscription[]
chats Chat[]
files File[]
@@unique([email])
@@map("user")
}
The payment system supports both authenticated users and guest checkouts:
model Payment {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
// Customer identification (works for both auth and authless modes)
customerEmail String
customerName String?
userId String? // Populated in auth mode, null in authless
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
// Processor info
processor String @default("stripe")
processorCustomerId String
processorPaymentId String? @unique
processorInvoiceId String?
processorPriceId String?
// Payment details
amount Int // Amount in cents
currency String @default("usd")
status String // succeeded, pending, failed, refunded
// Optional subscription reference
subscriptionId String? @db.Uuid
subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull)
// Metadata & license generation
metadata Json?
licenseKeys LicenseKey[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([userId])
@@index([customerEmail])
@@index([processorCustomerId])
@@index([subscriptionId])
@@index([status])
@@map("payment")
}
The Prisma client is available at lib/prisma.ts and uses the singleton pattern to prevent connection issues during development. Import it anywhere in your server code:
import prisma from '@@/lib/prisma'
export default defineEventHandler(async (event) => {
const user = await prisma.user.findUnique({
where: { email: 'user@example.com' },
})
return user
})
The template follows consistent patterns for database operations. Here are the key conventions used throughout the codebase.
Always filter by userId for user-owned resources using the authenticated user from context:
import prisma from '@@/lib/prisma'
export default defineEventHandler(async (event) => {
const userId = await requireAuth(event)
const chats = await prisma.chat.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
})
return chats
})
Use include to load related data efficiently:
const chat = await prisma.chat.findFirst({
where: {
id: chatId,
userId: userId, // Always scope to user
},
include: {
messages: {
orderBy: { createdAt: 'asc' },
},
},
})
For complex queries or reusable logic, use server services (server/services/):
import { getProducts } from '@@/server/services/products-server-service'
export default defineEventHandler(async (event) => {
const products = await getProducts({ page: 1, limit: 10 })
return products
})
For complete Prisma documentation, see the official Prisma docs.
The template includes a complete schema in prisma/schema.prisma. To initialize your database:
# Development - creates migration history
pnpm prisma migrate dev --name init
# Or use db push for rapid prototyping (no migration files)
pnpm prisma db push
This creates all tables (users, sessions, payments, subscriptions, chats, files, etc.) in your database.
When you add new models or modify existing ones:
prisma/schema.prismapnpm prisma migrate dev --name your_change_description
pnpm prisma migrate deploy to apply migrations without prompts.For detailed migration workflows, see the Prisma migrations documentation.
The template follows these naming conventions for consistency:
UserData, BugReport)user_data, bug_report)model BugReport {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
// ... fields
@@map("bug_report")
}
userId, createdAt, processorCustomerId)id, though some models use descriptive names (e.g., priceId)createdAt for all tables; add updatedAt for frequently modified dataWhen adding tables that belong to users, follow this structure:
model YourNewTable {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
userId String // Foreign key to User
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("your_new_table")
}
View and edit your database visually:
pnpm prisma studio
Opens at http://localhost:5555 - useful for debugging and testing during development.
Configure your database connection in .env:
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
For comprehensive Prisma documentation including transactions, error handling, performance optimization, and advanced queries: