Claude-skill-registry database-patterns
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/database-patterns-wania-kazmi-claude-code-autonomo" ~/.claude/skills/majiayu000-claude-skill-registry-database-patterns && rm -rf "$T"
manifest:
skills/data/database-patterns-wania-kazmi-claude-code-autonomo/SKILL.mdsource content
Database Patterns & Best Practices
Schema Design Principles
1. Normalization (3NF minimum)
- No repeating groups
- No partial dependencies
- No transitive dependencies
2. Use UUIDs vs Auto-Increment
-- GOOD: UUID for distributed systems id UUID PRIMARY KEY DEFAULT gen_random_uuid() -- OK: Auto-increment for simple cases id SERIAL PRIMARY KEY
3. Timestamps on Every Table
CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() );
Prisma ORM Patterns
Schema Definition
// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) email String @unique name String role Role @default(USER) posts Post[] profile Profile? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([email]) @@map("users") } model Post { id String @id @default(uuid()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId String tags Tag[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([authorId]) @@index([published]) @@map("posts") } enum Role { USER ADMIN }
CRUD Operations
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() // Create async function createUser(data: { name: string; email: string }) { return prisma.user.create({ data, select: { id: true, name: true, email: true, createdAt: true } }) } // Read with relations async function getUserWithPosts(id: string) { return prisma.user.findUnique({ where: { id }, include: { posts: { where: { published: true }, orderBy: { createdAt: 'desc' }, take: 10 }, profile: true } }) } // Update async function updateUser(id: string, data: Partial<User>) { return prisma.user.update({ where: { id }, data }) } // Delete (soft delete pattern) async function deleteUser(id: string) { return prisma.user.update({ where: { id }, data: { deletedAt: new Date() } }) }
Transactions
// Interactive transaction async function transferFunds(fromId: string, toId: string, amount: number) { return prisma.$transaction(async (tx) => { const from = await tx.account.update({ where: { id: fromId }, data: { balance: { decrement: amount } } }) if (from.balance < 0) { throw new Error('Insufficient funds') } await tx.account.update({ where: { id: toId }, data: { balance: { increment: amount } } }) return tx.transaction.create({ data: { fromId, toId, amount } }) }) } // Sequential transaction async function createUserWithProfile(data: CreateUserInput) { return prisma.$transaction([ prisma.user.create({ data: data.user }), prisma.profile.create({ data: data.profile }) ]) }
Pagination
// Cursor-based (recommended for large datasets) async function getUsers(cursor?: string, limit = 20) { const users = await prisma.user.findMany({ take: limit + 1, cursor: cursor ? { id: cursor } : undefined, orderBy: { createdAt: 'desc' }, skip: cursor ? 1 : 0 // Skip the cursor itself }) const hasMore = users.length > limit const data = hasMore ? users.slice(0, -1) : users return { data, nextCursor: hasMore ? data[data.length - 1].id : null } } // Offset-based async function getUsersWithOffset(page = 1, limit = 20) { const [users, total] = await Promise.all([ prisma.user.findMany({ skip: (page - 1) * limit, take: limit }), prisma.user.count() ]) return { data: users, meta: { total, page, limit, totalPages: Math.ceil(total / limit) } } }
Query Optimization
Avoid N+1 Queries
// BAD: N+1 problem const users = await prisma.user.findMany() for (const user of users) { const posts = await prisma.post.findMany({ where: { authorId: user.id } }) } // GOOD: Include in single query const users = await prisma.user.findMany({ include: { posts: true } }) // GOOD: Select only needed fields const users = await prisma.user.findMany({ select: { id: true, name: true, posts: { select: { id: true, title: true } } } })
Use Indexes Properly
model Post { id String @id authorId String status Status createdAt DateTime // Compound index for common query patterns @@index([authorId, status]) @@index([status, createdAt]) }
-- For frequently queried columns CREATE INDEX idx_posts_author_status ON posts(author_id, status); -- For text search CREATE INDEX idx_posts_title_gin ON posts USING gin(to_tsvector('english', title)); -- For JSON columns CREATE INDEX idx_metadata_gin ON posts USING gin(metadata);
Batch Operations
// Batch create await prisma.user.createMany({ data: users, skipDuplicates: true }) // Batch update with raw SQL (when needed) await prisma.$executeRaw` UPDATE posts SET status = 'archived' WHERE created_at < NOW() - INTERVAL '1 year' ` // Batch delete await prisma.user.deleteMany({ where: { deletedAt: { not: null }, deletedAt: { lt: thirtyDaysAgo } } })
Migration Patterns
Prisma Migrations
# Create migration npx prisma migrate dev --name add_user_role # Apply in production npx prisma migrate deploy # Reset database (dev only) npx prisma migrate reset
Safe Schema Changes
// Step 1: Add nullable column model User { newField String? // nullable first } // Step 2: Backfill data await prisma.$executeRaw` UPDATE users SET new_field = 'default' WHERE new_field IS NULL ` // Step 3: Make non-nullable model User { newField String @default("default") }
Rollback Strategy
-- Always create down migrations -- up.sql ALTER TABLE users ADD COLUMN phone VARCHAR(20); -- down.sql ALTER TABLE users DROP COLUMN phone;
Connection Management
Connection Pooling
// Singleton pattern for Prisma const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined } export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: ['error', 'warn'], datasources: { db: { url: process.env.DATABASE_URL } } }) if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma }
Serverless Configuration
// For serverless (Vercel, AWS Lambda) import { PrismaClient } from '@prisma/client' import { Pool } from '@neondatabase/serverless' import { PrismaNeon } from '@prisma/adapter-neon' const pool = new Pool({ connectionString: process.env.DATABASE_URL }) const adapter = new PrismaNeon(pool) const prisma = new PrismaClient({ adapter })
Soft Delete Pattern
model User { id String @id @default(uuid()) email String @unique deletedAt DateTime? @@index([deletedAt]) }
// Middleware for automatic filtering prisma.$use(async (params, next) => { if (params.model === 'User') { if (params.action === 'findMany' || params.action === 'findFirst') { params.args.where = { ...params.args.where, deletedAt: null } } } return next(params) }) // Soft delete async function softDelete(id: string) { return prisma.user.update({ where: { id }, data: { deletedAt: new Date() } }) } // Hard delete (permanent) async function hardDelete(id: string) { return prisma.user.delete({ where: { id } }) }
Audit Trail Pattern
model AuditLog { id String @id @default(uuid()) entityType String entityId String action String // CREATE, UPDATE, DELETE changes Json? userId String? createdAt DateTime @default(now()) @@index([entityType, entityId]) @@index([userId]) @@index([createdAt]) }
// Middleware for automatic audit logging prisma.$use(async (params, next) => { const result = await next(params) if (['create', 'update', 'delete'].includes(params.action)) { await prisma.auditLog.create({ data: { entityType: params.model!, entityId: result.id, action: params.action.toUpperCase(), changes: params.args.data, userId: getCurrentUserId() } }) } return result })
Multi-Tenant Pattern
model Organization { id String @id @default(uuid()) name String users User[] posts Post[] } model User { id String @id @default(uuid()) organization Organization @relation(fields: [organizationId], references: [id]) organizationId String @@index([organizationId]) }
// Row-level security with Prisma extension const prismaWithTenant = prisma.$extends({ query: { $allModels: { async $allOperations({ args, query, model }) { const tenantId = getTenantId() if (tenantId && hasTenantField(model)) { args.where = { ...args.where, organizationId: tenantId } } return query(args) } } } })
Raw SQL When Needed
// Complex aggregations const stats = await prisma.$queryRaw<Stats[]>` SELECT DATE_TRUNC('day', created_at) as date, COUNT(*) as count, SUM(amount) as total FROM transactions WHERE created_at >= ${startDate} GROUP BY DATE_TRUNC('day', created_at) ORDER BY date DESC ` // Full-text search const results = await prisma.$queryRaw<Post[]>` SELECT * FROM posts WHERE to_tsvector('english', title || ' ' || content) @@ plainto_tsquery('english', ${searchTerm}) ORDER BY ts_rank( to_tsvector('english', title || ' ' || content), plainto_tsquery('english', ${searchTerm}) ) DESC LIMIT ${limit} `
Checklist
- UUIDs for distributed systems
- Timestamps on all tables
- Proper indexes for query patterns
- N+1 queries avoided (use include/join)
- Transactions for multi-step operations
- Soft delete where appropriate
- Connection pooling configured
- Migrations tested (up and down)
- Audit logging for sensitive data
- Query performance monitored