Cursor Skill

database-design

Design database schemas — tables, relationships, indexes, constraints, and ORM setup. Covers relational design, normalization, and common patterns.

LLM Mart · 0 points · 22 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download spencerpauly-awesome-cursor-skills-resources_database-design-99cd265.zip · 1 KB
Part of spencerpauly/awesome-cursor-skills — 65 skills

Install

skills CLI npx skills add https://github.com/spencerpauly/awesome-cursor-skills/tree/main/resources/database-design
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install spencerpauly-awesome-cursor-skills@llmmart
Git git clone https://github.com/spencerpauly/awesome-cursor-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole spencerpauly/awesome-cursor-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

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 UUID for distributed systems or public-facing IDs
  • Use SERIAL/BIGSERIAL for internal-only IDs (faster joins)

Timestamps:

  • Always add created_at and updated_at
  • Use TIMESTAMPTZ (with timezone), never TIMESTAMP

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 NULL on everything unless it's genuinely optional
  • UNIQUE on natural keys (email, slug, external IDs)
  • REFERENCES with ON DELETE behavior (CASCADE, SET NULL, RESTRICT)
  • CHECK constraints 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
Files (awesome-cursor-skills)
  • SKILL.md 4 KB
    ---
    name: database-design
    description: Design database schemas — tables, relationships, indexes, constraints, and ORM setup. Covers relational design, normalization, and common patterns.
    user-invocable: true
    ---
    
    # 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:
    
    ```sql
    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 `UUID` for distributed systems or public-facing IDs
    - Use `SERIAL`/`BIGSERIAL` for internal-only IDs (faster joins)
    
    **Timestamps:**
    - Always add `created_at` and `updated_at`
    - Use `TIMESTAMPTZ` (with timezone), never `TIMESTAMP`
    
    **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 NULL` on everything unless it's genuinely optional
    - `UNIQUE` on natural keys (email, slug, external IDs)
    - `REFERENCES` with `ON DELETE` behavior (CASCADE, SET NULL, RESTRICT)
    - `CHECK` constraints for enums or value ranges
    
    ### 5. Add Indexes
    
    ```sql
    -- 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:**
    ```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:**
    ```typescript
    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
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related