database-design
Database Design
Design a database schema from requirements.
Workflow
1. Identify Entities
From the requirements, extract the core entities (nouns):
- Users, Teams, Projects, Tasks, Comments, etc.
- Each entity becomes a table
2. Define Relationships
| Relationship | Implementation |
|---|---|
| One-to-one | Foreign key with unique constraint, or embed in same table |
| One-to-many | Foreign key on the "many" side |
| Many-to-many | Junction/join table |
| Self-referential | Foreign key pointing to same table (e.g. parent_id) |
3. Design the Schema
For each table:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
4. Apply Best Practices
Primary keys:
- Use
UUIDfor distributed systems or public-facing IDs - Use
SERIAL/BIGSERIALfor internal-only IDs (faster joins)
Timestamps:
- Always add
created_atandupdated_at - Use
TIMESTAMPTZ(with timezone), neverTIMESTAMP
Naming:
- Tables: plural snake_case (
users,project_members) - Columns: singular snake_case (
user_id,created_at) - Indexes:
idx_<table>_<columns>(idx_users_email)
Constraints:
NOT NULLon everything unless it's genuinely optionalUNIQUEon natural keys (email, slug, external IDs)REFERENCESwithON DELETEbehavior (CASCADE, SET NULL, RESTRICT)CHECKconstraints for enums or value ranges
5. Add Indexes
-- For columns you filter/sort by frequently
CREATE INDEX idx_projects_owner_id ON projects(owner_id);
-- For unique lookups
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- Composite for common query patterns
CREATE INDEX idx_tasks_project_status ON tasks(project_id, status);
When to index:
- Foreign keys (almost always)
- Columns in WHERE clauses
- Columns in ORDER BY
- Columns in JOIN conditions
When NOT to index:
- Small tables (<1000 rows)
- Columns with low cardinality (boolean, status with 3 values)
- Columns that are rarely queried
6. ORM Setup
Prisma:
model User {
id String @id @default(uuid())
email String @unique
name String
projects Project[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
}
Drizzle:
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
});
Common Patterns
Soft deletes: Add deleted_at TIMESTAMPTZ instead of actually deleting rows
Audit log: Separate audit_events table with entity_type, entity_id, action, actor_id, payload
Tags/labels: Junction table (task_tags) with task_id + tag_id
Tree/hierarchy: parent_id self-reference, or materialized path (/1/4/7/)
Polymorphic associations: Use entity_type + entity_id columns (avoid if possible, prefer separate FKs)
Tips
- Start normalized (3NF), denormalize only when you have measured performance problems
- Don't store derived data unless you have a caching/invalidation strategy
- Use database enums or check constraints for status fields, not free-text
- Always think about what happens when you delete a parent record
More from spencerpauly/awesome-cursor-skills
reviewing-code
Perform a thorough code review focused on correctness, maintainability, performance, and best practices.
32saving-workspace-context
Automatically persist useful context — research, decisions, learnings, templates — to workspace files so knowledge survives across conversations.
31suggesting-cursor-rules
When the user repeats the same correction or convention multiple times, suggest a Cursor rule to encode it permanently.
31suggesting-cursor-hooks
When the user keeps asking for the same check to run (lint, tests, type-check), suggest a Cursor hook to automate it.
29monitoring-terminal-errors
Watch running terminal processes for crashes and stack traces. When an error appears, navigate to the failing file and line, diagnose, and fix it automatically.
28auditing-security
Perform a systematic security audit of a codebase, checking for OWASP Top 10 vulnerabilities, secrets exposure, and insecure patterns.
28