database-best-practices

Database Best Practices Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "database-best-practices" with this command: npx skills add sarojpunde/shopify-dev-toolkit-claude-plugins/sarojpunde-shopify-dev-toolkit-claude-plugins-database-best-practices

Database Best Practices Skill

Purpose

Provides best practices and patterns for database operations in Shopify apps using Prisma ORM, focusing on data isolation, query optimization, and safe migrations.

When This Skill Activates

  • Working with Prisma schema or queries

  • Creating database migrations

  • Optimizing database performance

  • Implementing multi-tenant data isolation

  • Handling transactions

Critical: Multi-Tenant Data Isolation

ALWAYS filter by shopId - This prevents data leaks between shops.

// ✅ CORRECT - Always include shopId const products = await db.product.findMany({ where: { shopId: shop.id, status: "active", }, });

// ❌ WRONG - Missing shopId (data leak!) const products = await db.product.findMany({ where: { status: "active" }, });

Core Patterns

  1. Safe Query Pattern

// Always filter by shopId for shop-specific data async function getShopProducts(shopId: string) { return db.product.findMany({ where: { shopId }, select: { id: true, title: true, vendor: true, // Only select needed fields }, take: 50, orderBy: { createdAt: "desc" }, }); }

  1. Pagination Pattern

async function getPaginatedProducts(shopId: string, page: number = 1) { const pageSize = 50; const skip = (page - 1) * pageSize;

const [products, totalCount] = await Promise.all([ db.product.findMany({ where: { shopId }, skip, take: pageSize, orderBy: { createdAt: "desc" }, }), db.product.count({ where: { shopId }, }), ]);

return { products, totalCount, totalPages: Math.ceil(totalCount / pageSize), currentPage: page, }; }

  1. Transaction Pattern

// Use transactions for operations that must succeed/fail together await db.$transaction(async (tx) => { // Update product await tx.product.update({ where: { id: productId }, data: { status: "synced" }, });

// Create audit log await tx.auditLog.create({ data: { shopId: shop.id, entityType: "product", entityId: productId, action: "update", changesSummary: "Product synced", }, }); });

  1. Upsert Pattern

// Create or update based on existence await db.product.upsert({ where: { shopId_productId: { shopId: shop.id, productId: shopifyProductId, }, }, create: { shopId: shop.id, productId: shopifyProductId, title: "Product Title", }, update: { title: "Updated Title", updatedAt: new Date(), }, });

  1. Bulk Operations

// Use createMany for bulk inserts await db.product.createMany({ data: products.map(p => ({ shopId: shop.id, productId: p.id, title: p.title, })), skipDuplicates: true, // Skip if unique constraint violated });

// Use updateMany for bulk updates await db.product.updateMany({ where: { shopId: shop.id, status: "pending", }, data: { status: "processed", updatedAt: new Date(), }, });

  1. JSON Field Handling

// Store complex data as JSON const metadata = { tags: ["summer", "sale"], featured: true };

await db.product.create({ data: { shopId: shop.id, title: "Product", metadata: JSON.stringify(metadata), }, });

// Retrieve and parse JSON const product = await db.product.findUnique({ where: { id: productId }, });

const metadata = JSON.parse(product.metadata || "{}");

  1. Error Handling

// Handle unique constraint violations try { await db.product.create({ data: { shopId, productId, title }, }); } catch (error) { if (error.code === "P2002") { // Unique constraint violation - update instead await db.product.update({ where: { shopId_productId: { shopId, productId } }, data: { title, updatedAt: new Date() }, }); } else { throw error; } }

  1. N+1 Query Prevention

// ❌ BAD: N+1 query problem const products = await db.product.findMany(); for (const product of products) { const shop = await db.shop.findUnique({ where: { id: product.shopId }, }); console.log(shop.shopDomain); }

// ✅ GOOD: Use include const products = await db.product.findMany({ include: { shop: { select: { shopDomain: true, }, }, }, });

Schema Design Patterns

Multi-Tenant Schema

// Base Shop model - required model Shop { id String @id @default(cuid()) shopDomain String @unique accessToken String installedAt DateTime @default(now())

// All shop-specific data references this products Product[] orders Order[] }

// Shop-specific model model Product { id String @id @default(cuid()) shopId String shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade) productId String // Shopify product ID title String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt

@@unique([shopId, productId]) @@index([shopId]) }

Common Indexes

// Essential indexes for performance @@index([shopId]) // Filter by shop @@index([shopId, status]) // Shop + status filter @@index([createdAt]) // Time-based queries @@index([updatedAt]) // Recently updated @@unique([shopId, externalId]) // Prevent duplicates per shop

Migration Best Practices

  1. Safe Migrations

Development - generates migration

npx prisma migrate dev --name add_vendor_field

Production - applies migrations

npx prisma migrate deploy

  1. Adding Fields Safely

// Step 1: Add as optional model Product { vendor String? // Optional first }

// Step 2: Backfill data // Run a script to populate vendor for existing records

// Step 3: Make required (in next migration) model Product { vendor String // Required after backfill }

  1. Data Migration Script

// scripts/backfill-vendor.ts async function backfillVendor() { const products = await db.product.findMany({ where: { vendor: null }, });

for (const product of products) { await db.product.update({ where: { id: product.id }, data: { vendor: extractVendorFromTitle(product.title), }, }); }

console.log(Updated ${products.length} products); }

Performance Optimization

  1. Select Only Needed Fields

// ❌ Inefficient - fetches all fields const products = await db.product.findMany();

// ✅ Efficient - only needed fields const products = await db.product.findMany({ select: { id: true, title: true, status: true, }, });

  1. Use Appropriate Indexes

// Index frequently queried fields @@index([shopId, status]) // Composite index for common query @@index([createdAt]) // Time-based sorting @@index([vendor]) // Filtering by vendor

  1. Batch Operations

// Process in batches to avoid memory issues const batchSize = 100; let skip = 0;

while (true) { const batch = await db.product.findMany({ where: { shopId: shop.id }, skip, take: batchSize, });

if (batch.length === 0) break;

await processBatch(batch); skip += batchSize; }

Common Prisma Errors

P2002 - Unique Constraint Violation

// Handle gracefully with upsert await db.product.upsert({ where: { shopId_productId: { shopId, productId } }, create: { shopId, productId, title }, update: { title, updatedAt: new Date() }, });

P2025 - Record Not Found

// Check existence first or use try/catch const product = await db.product.findUnique({ where: { id: productId }, });

if (!product) { throw new Response("Product not found", { status: 404 }); }

Best Practices Checklist

  • Always filter shop-specific queries by shopId

  • Use transactions for multi-step operations

  • Select only needed fields in queries

  • Add indexes for common query patterns

  • Handle unique constraint violations gracefully

  • Use upsert for create-or-update logic

  • Validate JSON data before storing

  • Test migrations in development first

  • Use cascade delete appropriately

  • Monitor query performance

Remember: Proper database practices prevent data leaks, improve performance, and ensure data integrity.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

polaris-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shopify workflow & tools

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shopify-api-patterns

No summary provided by upstream source.

Repository SourceNeeds Review