
Database Design
- 3 installs
- 132 repo stars
- Updated January 14, 2026
- xenitv1/antigravity-workflows
This is a copy of database-design by davila7 - installs and ranking accrue to the original listing.
Helps with design & ui/ux tasks.
About
database-design is a Claude Code skill for design & ui/ux. It helps solo builders move faster with AI-assisted development.
- database-design
- Design & UI/UX
- AI-coding skill
Database Design by the numbers
- 3 all-time installs (skills.sh)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xenitv1/antigravity-workflows --skill database-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 132 |
| Last updated | January 14, 2026 |
| Repository | xenitv1/antigravity-workflows ↗ |
What it does
Helps with design & ui/ux tasks.
Files
Database Design
Learn to THINK, not copy SQL patterns.
🎯 Selective Reading Rule
Read ONLY files relevant to the request! Check the content map, find what you need.
| File | Description | When to Read |
|---|---|---|
database-selection.md | PostgreSQL vs Neon vs Turso vs SQLite | Choosing database |
orm-selection.md | Drizzle vs Prisma vs Kysely | Choosing ORM |
schema-design.md | Normalization, PKs, relationships | Designing schema |
indexing.md | Index types, composite indexes | Performance tuning |
optimization.md | N+1, EXPLAIN ANALYZE | Query optimization |
migrations.md | Safe migrations, serverless DBs | Schema changes |
---
⚠️ Core Principle
- ASK user for database preferences when unclear
- Choose database/ORM based on CONTEXT
- Don't default to PostgreSQL for everything
---
Decision Checklist
Before designing schema:
- [ ] Asked user about database preference?
- [ ] Chosen database for THIS context?
- [ ] Considered deployment environment?
- [ ] Planned index strategy?
- [ ] Defined relationship types?
---
Anti-Patterns
❌ Default to PostgreSQL for simple apps (SQLite may suffice) ❌ Skip indexing ❌ Use SELECT * in production ❌ Store JSON when structured data is better ❌ Ignore N+1 queries
Database Selection (2025)
Choose database based on context, not default.
Decision Tree
What are your requirements?
│
├── Full relational features needed
│ ├── Self-hosted → PostgreSQL
│ └── Serverless → Neon, Supabase
│
├── Edge deployment / Ultra-low latency
│ └── Turso (edge SQLite)
│
├── AI / Vector search
│ └── PostgreSQL + pgvector
│
├── Simple / Embedded / Local
│ └── SQLite
│
└── Global distribution
└── PlanetScale, CockroachDB, TursoComparison
| Database | Best For | Trade-offs |
|---|---|---|
| PostgreSQL | Full features, complex queries | Needs hosting |
| Neon | Serverless PG, branching | PG complexity |
| Turso | Edge, low latency | SQLite limitations |
| SQLite | Simple, embedded, local | Single-writer |
| PlanetScale | MySQL, global scale | No foreign keys |
Questions to Ask
1. What's the deployment environment? 2. How complex are the queries? 3. Is edge/serverless important? 4. Vector search needed? 5. Global distribution required?
Indexing Principles
When and how to create indexes effectively.
When to Create Indexes
Index these:
├── Columns in WHERE clauses
├── Columns in JOIN conditions
├── Columns in ORDER BY
├── Foreign key columns
└── Unique constraints
Don't over-index:
├── Write-heavy tables (slower inserts)
├── Low-cardinality columns
├── Columns rarely queriedIndex Type Selection
| Type | Use For |
|---|---|
| B-tree | General purpose, equality & range |
| Hash | Equality only, faster |
| GIN | JSONB, arrays, full-text |
| GiST | Geometric, range types |
| HNSW/IVFFlat | Vector similarity (pgvector) |
Composite Index Principles
Order matters for composite indexes:
├── Equality columns first
├── Range columns last
├── Most selective first
└── Match query patternMigration Principles
Safe migration strategy for zero-downtime changes.
Safe Migration Strategy
For zero-downtime changes:
│
├── Adding column
│ └── Add as nullable → backfill → add NOT NULL
│
├── Removing column
│ └── Stop using → deploy → remove column
│
├── Adding index
│ └── CREATE INDEX CONCURRENTLY (non-blocking)
│
└── Renaming column
└── Add new → migrate data → deploy → drop oldMigration Philosophy
- Never make breaking changes in one step
- Test migrations on data copy first
- Have rollback plan
- Run in transaction when possible
Serverless Databases
Neon (Serverless PostgreSQL)
| Feature | Benefit |
|---|---|
| Scale to zero | Cost savings |
| Instant branching | Dev/preview |
| Full PostgreSQL | Compatibility |
| Autoscaling | Traffic handling |
Turso (Edge SQLite)
| Feature | Benefit |
|---|---|
| Edge locations | Ultra-low latency |
| SQLite compatible | Simple |
| Generous free tier | Cost |
| Global distribution | Performance |
Query Optimization
N+1 problem, EXPLAIN ANALYZE, optimization priorities.
N+1 Problem
What is N+1?
├── 1 query to get parent records
├── N queries to get related records
└── Very slow!
Solutions:
├── JOIN → Single query with all data
├── Eager loading → ORM handles JOIN
├── DataLoader → Batch and cache (GraphQL)
└── Subquery → Fetch related in one queryQuery Analysis Mindset
Before optimizing:
├── EXPLAIN ANALYZE the query
├── Look for Seq Scan (full table scan)
├── Check actual vs estimated rows
└── Identify missing indexesOptimization Priorities
1. Add missing indexes (most common issue) 2. Select only needed columns (not SELECT ) 3. Use proper JOINs (avoid subqueries when possible) 4. Limit early (pagination at database level) 5. Cache* (when appropriate)
ORM Selection (2025)
Choose ORM based on deployment and DX needs.
Decision Tree
What's the context?
│
├── Edge deployment / Bundle size matters
│ └── Drizzle (smallest, SQL-like)
│
├── Best DX / Schema-first
│ └── Prisma (migrations, studio)
│
├── Maximum control
│ └── Raw SQL with query builder
│
└── Python ecosystem
└── SQLAlchemy 2.0 (async support)Comparison
| ORM | Best For | Trade-offs |
|---|---|---|
| Drizzle | Edge, TypeScript | Newer, less examples |
| Prisma | DX, schema management | Heavier, not edge-ready |
| Kysely | Type-safe SQL builder | Manual migrations |
| Raw SQL | Complex queries, control | Manual type safety |
Schema Design Principles
Normalization, primary keys, timestamps, relationships.
Normalization Decision
When to normalize (separate tables):
├── Data is repeated across rows
├── Updates would need multiple changes
├── Relationships are clear
└── Query patterns benefit
When to denormalize (embed/duplicate):
├── Read performance critical
├── Data rarely changes
├── Always fetched together
└── Simpler queries neededPrimary Key Selection
| Type | Use When |
|---|---|
| UUID | Distributed systems, security |
| ULID | UUID + sortable by time |
| Auto-increment | Simple apps, single database |
| Natural key | Rarely (business meaning) |
Timestamp Strategy
For every table:
├── created_at → When created
├── updated_at → Last modified
└── deleted_at → Soft delete (if needed)
Use TIMESTAMPTZ (with timezone) not TIMESTAMPRelationship Types
| Type | When | Implementation |
|---|---|---|
| One-to-One | Extension data | Separate table with FK |
| One-to-Many | Parent-children | FK on child table |
| Many-to-Many | Both sides have many | Junction table |
Foreign Key ON DELETE
├── CASCADE → Delete children with parent
├── SET NULL → Children become orphans
├── RESTRICT → Prevent delete if children exist
└── SET DEFAULT → Children get default value#!/usr/bin/env python3
"""
Schema Validator - Database schema validation
Validates Prisma schemas and checks for common issues.
Usage:
python schema_validator.py <project_path>
Checks:
- Prisma schema syntax
- Missing relations
- Index recommendations
- Naming conventions
"""
import sys
import json
import re
from pathlib import Path
from datetime import datetime
# Fix Windows console encoding
try:
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
except:
pass
def find_schema_files(project_path: Path) -> list:
"""Find database schema files."""
schemas = []
# Prisma schema
prisma_files = list(project_path.glob('**/prisma/schema.prisma'))
schemas.extend([('prisma', f) for f in prisma_files])
# Drizzle schema files
drizzle_files = list(project_path.glob('**/drizzle/*.ts'))
drizzle_files.extend(project_path.glob('**/schema/*.ts'))
for f in drizzle_files:
if 'schema' in f.name.lower() or 'table' in f.name.lower():
schemas.append(('drizzle', f))
return schemas[:10] # Limit
def validate_prisma_schema(file_path: Path) -> list:
"""Validate Prisma schema file."""
issues = []
try:
content = file_path.read_text(encoding='utf-8', errors='ignore')
# Find all models
models = re.findall(r'model\s+(\w+)\s*{([^}]+)}', content, re.DOTALL)
for model_name, model_body in models:
# Check naming convention (PascalCase)
if not model_name[0].isupper():
issues.append(f"Model '{model_name}' should be PascalCase")
# Check for id field
if '@id' not in model_body and 'id' not in model_body.lower():
issues.append(f"Model '{model_name}' might be missing @id field")
# Check for createdAt/updatedAt
if 'createdAt' not in model_body and 'created_at' not in model_body:
issues.append(f"Model '{model_name}' missing createdAt field (recommended)")
# Check for @relation without fields
relations = re.findall(r'@relation\([^)]*\)', model_body)
for rel in relations:
if 'fields:' not in rel and 'references:' not in rel:
pass # Implicit relation, ok
# Check for @@index suggestions
foreign_keys = re.findall(r'(\w+Id)\s+\w+', model_body)
for fk in foreign_keys:
if f'@@index([{fk}])' not in content and f'@@index(["{fk}"])' not in content:
issues.append(f"Consider adding @@index([{fk}]) for better query performance in {model_name}")
# Check for enum definitions
enums = re.findall(r'enum\s+(\w+)\s*{', content)
for enum_name in enums:
if not enum_name[0].isupper():
issues.append(f"Enum '{enum_name}' should be PascalCase")
except Exception as e:
issues.append(f"Error reading schema: {str(e)[:50]}")
return issues
def main():
project_path = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
print(f"\n{'='*60}")
print(f"[SCHEMA VALIDATOR] Database Schema Validation")
print(f"{'='*60}")
print(f"Project: {project_path}")
print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("-"*60)
# Find schema files
schemas = find_schema_files(project_path)
print(f"Found {len(schemas)} schema files")
if not schemas:
output = {
"script": "schema_validator",
"project": str(project_path),
"schemas_checked": 0,
"issues_found": 0,
"passed": True,
"message": "No schema files found"
}
print(json.dumps(output, indent=2))
sys.exit(0)
# Validate each schema
all_issues = []
for schema_type, file_path in schemas:
print(f"\nValidating: {file_path.name} ({schema_type})")
if schema_type == 'prisma':
issues = validate_prisma_schema(file_path)
else:
issues = [] # Drizzle validation could be added
if issues:
all_issues.append({
"file": str(file_path.name),
"type": schema_type,
"issues": issues
})
# Summary
print("\n" + "="*60)
print("SCHEMA ISSUES")
print("="*60)
if all_issues:
for item in all_issues:
print(f"\n{item['file']} ({item['type']}):")
for issue in item["issues"][:5]: # Limit per file
print(f" - {issue}")
if len(item["issues"]) > 5:
print(f" ... and {len(item['issues']) - 5} more issues")
else:
print("No schema issues found!")
total_issues = sum(len(item["issues"]) for item in all_issues)
# Schema issues are warnings, not failures
passed = True
output = {
"script": "schema_validator",
"project": str(project_path),
"schemas_checked": len(schemas),
"issues_found": total_issues,
"passed": passed,
"issues": all_issues
}
print("\n" + json.dumps(output, indent=2))
sys.exit(0)
if __name__ == "__main__":
main()