Provides guidance for designing database schemas for full-stack applications.
/plugin marketplace add gaurangrshah/gsc-plugins/plugin install appgen@gsc-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Provides guidance for designing database schemas for full-stack applications.
This skill helps the appgen agent design sound database schemas with proper relationships, types, constraints, and indexes.
Extract entities from requirements:
Example Requirement: "Inventory management system for a warehouse"
Entities:
For each entity, define:
?)Example:
model Product {
id String @id @default(cuid())
name String
sku String @unique
description String?
quantity Int @default(0)
categoryId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Identify relationships:
One-to-Many:
Many-to-Many:
Prisma Syntax:
One-to-Many:
model Category {
id String @id @default(cuid())
name String
products Product[] // Array on "many" side
}
model Product {
id String @id @default(cuid())
categoryId String
category Category @relation(fields: [categoryId], references: [id])
}
Many-to-Many (Explicit Join Table):
model Product {
id String @id @default(cuid())
locations ProductLocation[]
}
model Location {
id String @id @default(cuid())
products ProductLocation[]
}
model ProductLocation {
productId String
locationId String
quantity Int
product Product @relation(fields: [productId], references: [id])
location Location @relation(fields: [locationId], references: [id])
@@id([productId, locationId])
@@index([productId])
@@index([locationId])
}
Add indexes for:
Example:
model Product {
id String @id @default(cuid())
sku String @unique
categoryId String
category Category @relation(fields: [categoryId], references: [id])
@@index([categoryId]) // Index on foreign key
@@index([sku]) // Index on frequently searched field
}
| Prisma Type | Database Type | Use Case |
|---|---|---|
String | VARCHAR | Text fields, IDs |
Int | INTEGER | Numbers, counts |
BigInt | BIGINT | Large numbers |
Float | DOUBLE | Decimals |
Decimal | DECIMAL | Money (precise) |
Boolean | BOOLEAN | True/false |
DateTime | TIMESTAMP | Dates and times |
Json | JSON | Unstructured data |
| Attribute | Purpose | Example |
|---|---|---|
@id | Primary key | id String @id |
@default() | Default value | @default(cuid()) |
@unique | Unique constraint | email String @unique |
@updatedAt | Auto-update timestamp | updatedAt DateTime @updatedAt |
@relation() | Foreign key | @relation(fields: [userId], references: [id]) |
@@index() | Index | @@index([email]) |
@@unique() | Composite unique | @@unique([email, tenantId]) |
| Function | Purpose | Example |
|---|---|---|
cuid() | Random ID (short) | @default(cuid()) |
uuid() | Random ID (standard) | @default(uuid()) |
autoincrement() | Auto-increment number | @default(autoincrement()) |
now() | Current timestamp | @default(now()) |
model User {
id String @id @default(cuid())
email String @unique
passwordHash String // Use bcrypt or argon2
name String?
emailVerified DateTime?
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
accounts Account[]
@@index([email])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model Tenant {
id String @id @default(cuid())
name String
users User[]
}
model User {
id String @id @default(cuid())
email String
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([email, tenantId]) // Email unique per tenant
@@index([tenantId])
}
model Product {
id String @id @default(cuid())
name String
deletedAt DateTime? // Null = not deleted
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([deletedAt])
}
model Product {
id String @id @default(cuid())
name String
createdBy String
updatedBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
creator User @relation("ProductCreator", fields: [createdBy], references: [id])
updater User? @relation("ProductUpdater", fields: [updatedBy], references: [id])
@@index([createdBy])
@@index([updatedBy])
}
Before finalizing a schema:
?@id)onDelete: Cascade)passwordHash (not password)# Generate Prisma client
npx prisma generate
# Create initial migration
npx prisma migrate dev --name init
# Apply migration
npx prisma migrate deploy
npx prisma migrate dev --name description_of_changeCreate prisma/seed.ts:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Create seed data
const category = await prisma.category.create({
data: {
name: 'Electronics',
},
});
console.log({ category });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
Add to package.json:
{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}
If using Drizzle instead of Prisma:
// schema.ts
import { pgTable, text, timestamp, integer, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
}, (table) => ({
emailIdx: index('email_idx').on(table.email),
}));
export const posts = pgTable('posts', {
id: text('id').primaryKey(),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: text('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
}, (table) => ({
authorIdx: index('author_idx').on(table.authorId),
}));
Save schema documentation to database/schema.md:
# Database Schema
## Overview
[Brief description of the database purpose]
## Entities
### User
Represents warehouse staff members.
**Fields:**
- \`id\` (String) - Primary key
- \`email\` (String) - Unique email address
- \`name\` (String) - Full name
- \`role\` (Enum) - USER | ADMIN
- \`createdAt\` (DateTime) - Account creation date
- \`updatedAt\` (DateTime) - Last update date
**Relationships:**
- Has many InventoryTransactions
**Indexes:**
- email
### Product
Represents items in the warehouse inventory.
[Continue for each entity...]
## Relationships
- User → InventoryTransaction (one-to-many)
- Category → Product (one-to-many)
- Product ↔ Location (many-to-many via ProductLocation)
## Migrations
### Initial Schema (YYYY-MM-DD)
Created base schema with User, Product, Category, Location, InventoryTransaction entities.
[Document each migration...]
@@index([foreignKeyId])v1.0 (2024-12-13)
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.