database-design
Database design principles and decision-making. Schema design, indexing strategy, ORM selection, serverless databases.
Install
npx skills add https://github.com/VoDaiLocz/kilo-kit-mcp/tree/main/skills/engineering/database-design
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vodailocz-kilo-kit-mcp@llmmart
git clone https://github.com/VoDaiLocz/kilo-kit-mcp.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vodailocz/kilo-kit-mcp collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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
Files (kilo-kit-mcp)
-
scripts
-
schema_validator.py 5.2 KB
#!/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()
-
-
database-selection.md 1.1 KB
# 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, Turso ``` ## Comparison | 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.md 892 B
# 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 queried ``` ## Index 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 pattern ``` -
migrations.md 1.1 KB
# Migration 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 old ``` ## Migration 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 | -
optimization.md 902 B
# 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 query ``` ## Query Analysis Mindset ``` Before optimizing: ├── EXPLAIN ANALYZE the query ├── Look for Seq Scan (full table scan) ├── Check actual vs estimated rows └── Identify missing indexes ``` ## Optimization 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.md 771 B
# 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.md 1.4 KB
# 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 needed ``` ## Primary 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 TIMESTAMP ``` ## Relationship 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 ``` -
SKILL.md 1.5 KB
--- name: database-design description: Database design principles and decision-making. Schema design, indexing strategy, ORM selection, serverless databases. allowed-tools: Read, Write, Edit, Glob, Grep --- # 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
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.