PromptShop

Database Schema Designer

The Database Schema Designer skill designs relational database schemas from requirements and generates migrations and types. It handles multi-tenancy, soft d...

Install

npx promptshop add database-schema-designer

Details

What This Skill Does

  • The Database Schema Designer skill designs relational database schemas from requirements and generates migrations and types.
  • It handles multi-tenancy, soft deletes, audit trails, versioning, and polymorphic associations.
  • This skill is useful for backend engineers and data architects.

When to Use

  • Designing a new feature that needs database tables.
  • Reviewing a schema for performance issues.
  • Adding multi-tenancy to an existing schema.
  • Generating Type.
  • Script types from a Prisma schema.
  • Planning a schema migration for a breaking change.
  • Generating seed data.

Key Features

  • Normalizes requirements into tables and relationships.
  • Generates migrations for Drizzle, Prisma, Type.
  • ORM, Alembic.
  • Generates Type.
  • Script interfaces and Python dataclasses.
  • Creates Row-Level Security (RLS) policies.
  • Suggests composite, partial, and covering indexes.
  • Generates Mermaid diagram from schema.

Manual Installation

Database Schema Designer

Tier: POWERFUL
Category: Engineering
Domain: Data Architecture / Backend

Overview

  • Design relational database schemas from requirements and generate migrations, TypeScript/Python types, seed data, RLS policies, and indexes.
  • Handles multi-tenancy, soft deletes, audit trails, versioning, and polymorphic associations.

Core Capabilities

Schema design — normalize requirements into tables, relationships, constraints Migration generation — Drizzle, Prisma, TypeORM, Alembic Type generation — TypeScript interfaces, Python dataclasses/Pydantic models RLS policies — Row-Level Security for multi-tenant apps Index strategy — composite indexes, partial indexes, covering indexes Seed data — realistic test data generation ERD generation — Mermaid diagram from schema

Designing a new feature that needs database tables Reviewing a schema for performance or normalization issues Adding multi-tenancy to an existing schema Generating TypeScript types from a Prisma schema Planning a schema migration for a breaking change

Schema Design Process

Step 1: Requirements → Entities

Given requirements:

  • "Users can create projects.

  • Each project has tasks.
  • Tasks can have labels.
  • Tasks can be assigned to users.
  • We need a full audit trail.
  • ".

Extract entities:

  • User, Project, Task, Label, Task.
  • Label (junction), Task.
  • Assignment, Audit.
  • Log.

Step 2: Identify Relationships

User 1──* Project (owner) Project 1──* Task Task ── Label (via TaskLabel) Task ── User (via TaskAssignment) User 1──* AuditLog

Step 3: Add Cross-cutting Concerns

Multi-tenancy: add organization_id to all tenant-scoped tables Soft deletes: add deleted_at TIMESTAMPTZ instead of hard deletes Audit trail: add created_by, updated_by, created_at, updated_at Versioning: add version INTEGER for optimistic locking

Full Schema Example (Task Management SaaS)

→ See references/full-schema-examples.md for details

Row-Level Security (RLS) Policies

-- Enable RLS ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

-- Create app role CREATE ROLE app_user;

-- Users can only see tasks in their organization's projects CREATE POLICY tasks_org_isolation ON tasks FOR ALL TO app_user USING ( project_id IN ( SELECT p.id FROM projects p JOIN organization_members om ON om.organization_id = p.organization_id WHERE om.user_id = current_setting('app.current_user_id')::text ) );

-- Soft delete: never show deleted records CREATE POLICY tasks_no_deleted ON tasks FOR SELECT TO app_user USING (deleted_at IS NULL);

-- Only task creator or admin can delete CREATE POLICY tasks_delete_policy ON tasks FOR DELETE TO app_user USING ( created_by_id = current_setting('app.current_user_id')::text OR EXISTS ( SELECT 1 FROM organization_members om JOIN projects p ON p.organization_id = om.organization_id WHERE p.id = tasks.project_id AND om.user_id = current_setting('app.current_user_id')::text AND om.role IN ('owner', 'admin') ) );

-- Set user context (call at start of each request) SELECT set_config('app.current_user_id', $1, true);

Seed Data Generation

// db/seed.ts import { faker } from '@faker-js/faker' import { db } from './client' import { organizations, users, projects, tasks } from './schema' import { createId } from '@paralleldrive/cuid2' import { hashPassword } from '../src/lib/auth'

async function seed() { console.log('Seeding database...')

// Create org const [org] = await db.insert(organizations).values({ id: createId(), name: "acme-corp", slug: 'acme', plan: 'growth', }).returning()

// Create users const adminUser = await db.insert(users).values({ id: createId(), email: 'admin@acme.com', name: "alice-admin", passwordHash: await hashPassword('password123'), }).returning().then(r => r[0])

// Create projects const projectsData = Array.from({ length: 3 }, () => ({ id: createId(), organizationId: org.id, ownerId: adminUser.id, name: "fakercompanycatchphrase" description: faker.lorem.paragraph(), status: 'active' as const, }))

  • const createdProjects = await db.

  • insert(projects).

  • values(projectsData).

  • returning().

    // Create tasks for each project for (const project of createdProjects) { const tasksData = Array.from({ length: faker.number.int({ min: 5, max: 20 }) }, (_, i) => ({ id: createId(), projectId: project.id, title: faker.hacker.phrase(), description: faker.lorem.sentences(2), status: faker.helpers.arrayElement(['todo', 'in_progress', 'done'] as const), priority: faker.helpers.arrayElement(['low', 'medium', 'high'] as const), position: i * 1000, createdById: adminUser.id, updatedById: adminUser.id, }))

    await db.insert(tasks).values(tasksData) }

    console.log(✅ Seeded: 1 org, ${projectsData.length} projects, tasks) }

  • seed().

  • catch(console.

  • error).

  • finally(() => process.

  • exit(0)).

ERD Generation (Mermaid)

erDiagram Organization ||--o{ OrganizationMember : has Organization ||--o{ Project : owns User ||--o{ OrganizationMember : joins User ||--o{ Task : "created by" Project ||--o{ Task : contains Task ||--o{ TaskAssignment : has Task ||--o{ TaskLabel : has Task ||--o{ Comment : has Task ||--o{ Attachment : has Label ||--o{ TaskLabel : "applied to" User ||--o{ TaskAssignment : assigned

Organization {
    string id PK
    string name
    string slug
    string plan
}

Task {
    string id PK
    string project_id FK
    string title
    string status
    string priority
    timestamp due_date
    timestamp deleted_at
    int version
}

Generate from Prisma: npx prisma-erd-generator or: npx @dbml/cli prisma2dbml -i schema.prisma | npx dbml-to-mermaid

Common Pitfalls

Soft delete without index — WHERE deleted_at IS NULL without index = full scan Missing composite indexes — WHERE org_id = ? AND status = ? needs a composite index Mutable surrogate keys — never use email or slug as PK; use UUID/CUID Non-nullable without default — adding a NOT NULL column to existing table requires default or migration plan No optimistic locking — concurrent updates overwrite each other; add version column RLS not tested — always test RLS with a non-superuser role

Best Practices

Timestamps everywhere — created_at, updated_at on every table Soft deletes for auditable data — deleted_at instead of DELETE Audit log for compliance — log before/after JSON for regulated domains UUIDs or CUIDs as PKs — avoid sequential integer leakage Index foreign keys — every FK column should have an index Partial indexes — use WHERE deleted_at IS NULL for active-only queries RLS over application-level filtering — database enforces tenancy, not just app codeDatabase Schema Designer

Tier: POWERFUL
Category