prisma-relations-mapping

majiayu000's avatarfrom majiayu000

CRITICAL: Use this skill when working with Prisma database models, API routes with includes/relations, or debugging "500 errors", "relation not found", "undefined from backend" errors.AUTO-ACTIVATE when user mentions (FR/EN): - prisma, relation, relations, mapping, PascalCase, camelCase - API 500, erreur 500, backend error, erreur backend - undefined, null, relation introuvable, relation not found - créer API, create API, endpoint, route API - include, select, findMany, findUnique, create, update - OperatingRoom, OperatingSector, Personnel, Assignment, SiteThis skill contains the COMPLETE mapping table for post-PascalCase migration (Oct 26, 2025). ALL 121 models with correct relation names and required fields.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 5, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive mapping and debugging framework for Prisma ORM, specifically optimized for post-migration PascalCase environments. It helps developers navigate complex database relations, resolve '500 Internal Server Errors,' and ensure seamless data flow between backend Prisma models and frontend API responses through standardized naming conventions and reusable mapping patterns.

Use Cases

  • Case 1: Debugging 'relation not found' or 'undefined' errors in Prisma queries after a database schema migration or naming convention update.
  • Case 2: Implementing API routes that require complex nested 'includes' while maintaining strict mapping between PascalCase database models and camelCase frontend expectations.
  • Case 3: Ensuring data integrity during record creation by identifying required fields without default values, such as custom ID generation and mandatory timestamps.
  • Case 4: Standardizing development across large-scale projects with over 100+ models to ensure consistent naming for parents (PascalCase) and collections (snake_case).
nameprisma-relations-mapping
description>
CRITICALUse this skill when working with Prisma database models, API routes with includes/relations,

Prisma Relations Mapping - Post-Migration PascalCase

Version: 1.0 Migration: commit 3d353115d (26 Oct 2025) Purpose: Éviter erreurs 500 et mapping incorrects après migration PascalCase


🎯 Problems Solved

❌ Common Errors Without This Guide

// ❌ ERROR 500 - Relation doesn't exist after migration
const rooms = await prisma.operatingRoom.findMany({
  include: { operatingSector: true }  // ❌ TypeError: relation not found
});

// ❌ Frontend receives undefined - Incorrect mapping
return NextResponse.json(rooms);
// Frontend: room.operatingSector = undefined (expects camelCase)

✅ Solutions Here

  1. Complete relation conversion table (PascalCase ↔ camelCase)
  2. API → Frontend mapping patterns
  3. Required fields without @default per model
  4. ID generation patterns

📊 Naming Rules (Post-Migration)

1. Relations "to parent" (1:1, N:1) → PascalCase
   Example: OperatingSector, Personnel, User

2. Collections (1:N) → snake_case
   Example: operating_rooms, assignments, planned_absences

3. Frontend expectations → camelCase
   Example: operatingSector, site, rooms

🔥 TOP 5 CRITICAL MODELS (Quick Reference)

1. OperatingRoom

// ✅ Prisma Query
const rooms = await prisma.operatingRoom.findMany({
  include: {
    OperatingSector: true,  // ← PascalCase (to parent)
    sites: true             // ← PascalCase (to parent)
  }
});

// ✅ Frontend Mapping
const mappedRooms = rooms.map(room => ({
  ...room,
  operatingSector: room.OperatingSector,  // → camelCase
  site: room.sites                         // Rename sites → site
}));

return NextResponse.json(mappedRooms);

Reference: src/app/api/admin/rooms-order/route.ts:19-38


2. OperatingSector

// ✅ Prisma Query
const sectors = await prisma.operatingSector.findMany({
  include: {
    sites: true,                     // ← PascalCase (to parent)
    operating_rooms: {               // ← snake_case (collection)
      orderBy: [{ displayOrder: 'asc' }]
    },
    _count: {
      select: { operating_rooms: true }
    }
  }
});

// ✅ Frontend Mapping
const mappedSectors = sectors.map(sector => ({
  ...sector,
  site: sector.sites,                          // Rename sites → site
  _count: {
    rooms: sector._count.operating_rooms       // Rename
  }
}));

Reference: src/app/api/admin/sectors-order/route.ts:12-43


3. Site

// ✅ Prisma Query
const sites = await prisma.site.findMany({
  include: {
    OperatingSector: true,           // ← PascalCase (collection)
    operating_rooms: true,            // ← snake_case (collection)
    _count: {
      select: {
        OperatingSector: true,
        operating_rooms: true
      }
    }
  }
});

// ✅ Frontend Mapping (for _count)
const mapped = sites.map(site => ({
  ...site,
  _count: {
    operatingSectors: site._count.OperatingSector,
    operatingRooms: site._count.operating_rooms
  }
}));

Reference: src/app/api/admin/sites-order/route.ts:7-37


4. Assignment

// ✅ Query with nested relations
const assignments = await prisma.assignment.findMany({
  where: { date: { gte: startDate, lte: endDate } },
  include: {
    User: {                          // ← PascalCase
      select: { id: true, name: true, professionalRole: true }
    },
    TrameModele: true                // ← PascalCase
  }
});

// ✅ Frontend expects camelCase
const mapped = assignments.map(a => ({
  ...a,
  user: a.User,
  trameModele: a.TrameModele
}));

5. Personnel

// ✅ Query with relations
const personnel = await prisma.personnel.findMany({
  include: {
    User: true,                      // ← PascalCase
    Site: true,                      // ← PascalCase
    Specialty: true,                 // ← PascalCase
    planned_absences: true           // ← snake_case (collection)
  }
});

// ✅ Map if frontend needs camelCase
const mapped = personnel.map(p => ({
  ...p,
  user: p.User,
  site: p.Site,
  specialty: p.Specialty,
  plannedAbsences: p.planned_absences
}));

⚠️ Important: No direct FK personnelId in Assignment. Relation: Personnel → User → Assignment (via userId)


🔑 Required Fields Without @default

Site (id: String)

// ✅ Pattern tested
const siteId = `site-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;

await prisma.site.create({
  data: {
    id: siteId,              // ⚠️ REQUIRED
    name: 'Clinique Mathilde',
    address: '...',
    updatedAt: new Date(),   // ⚠️ REQUIRED
    displayOrder: 999,
    isActive: true
  }
});

Reference: src/app/api/admin/sites-order/route.ts:98-111


OperatingSector (id: Int @autoincrement)

await prisma.operatingSector.create({
  data: {
    name: 'Bloc Central',
    siteId: 'site-123',
    category: 'STANDARD',      // ⚠️ REQUIRED (enum, no @default)
    updatedAt: new Date(),     // ⚠️ REQUIRED
    displayOrder: 999,
    isActive: true
  }
});

Reference: src/app/api/admin/sectors-order/route.ts:107-119


OperatingRoom (id: Int @autoincrement)

// ✅ Pattern with fallback auto-generation
const roomNumber = data.number || `SALLE-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;

await prisma.operatingRoom.create({
  data: {
    name: 'Salle 1',
    number: roomNumber,           // ⚠️ User-editable or auto-gen
    operatingSectorId: 1,
    siteId: 'site-123',
    roomType: 'STANDARD',
    updatedAt: new Date(),        // ⚠️ REQUIRED
    displayOrder: 999,
    isActive: true,
    capacity: 1
  }
});

Reference: src/app/api/admin/rooms-order/route.ts:106-122


🛠️ Reusable Mapping Patterns

Pattern 1: Simple Rename

// Case: Single relation to rename
const items = await prisma.model.findMany({
  include: { UpperCaseRelation: true }
});

const mapped = items.map(item => ({
  ...item,
  lowerCaseRelation: item.UpperCaseRelation
}));

Pattern 2: _count Mapping

// Case: Count collections with different names
const items = await prisma.model.findMany({
  include: {
    _count: {
      select: {
        UpperCaseCollection: true,
        snake_case_collection: true
      }
    }
  }
});

const mapped = items.map(item => ({
  ...item,
  _count: {
    lowerCaseCollection: item._count.UpperCaseCollection,
    camelCaseCollection: item._count.snake_case_collection
  }
}));

Pattern 3: Nested Relations

// Case: Multi-level nested relations
const items = await prisma.model.findMany({
  include: {
    ParentRelation: {
      include: {
        GrandParentRelation: true
      }
    },
    child_relations: true
  }
});

const mapped = items.map(item => ({
  ...item,
  parentRelation: {
    ...item.ParentRelation,
    grandParentRelation: item.ParentRelation?.GrandParentRelation
  },
  childRelations: item.child_relations
}));

✅ Checklist for CRUD API Creation

GET Endpoint

  • Use correct PascalCase/snake_case relation names in include
  • Map _count to camelCase if needed
  • Rename relations for frontend (UpperCase → camelCase)
  • Test with Postman/curl before frontend integration

POST Endpoint

  • Check all required fields without @default in schema
  • Generate ID if needed (Site, BlocDayPlanning)
  • Add updatedAt: new Date() if required
  • Add default values for required enums (e.g., category: 'STANDARD')
  • Parse Int if FK is integer (parseInt(data.sectorId))

PATCH Endpoint

  • Always include updatedAt: new Date() in data
  • Parse Int for integer FKs
  • Validate required fields don't become null

DELETE Endpoint

  • Check FK constraints (cascade delete configured?)
  • Parse Int if ID is integer

🚨 Common Errors & Solutions

Error 1: PrismaClientKnownRequestError: Invalid relation name

❌ const rooms = await prisma.operatingRoom.findMany({
  include: { operatingSector: true }  // ❌ Doesn't exist after migration
});

✅ const rooms = await prisma.operatingRoom.findMany({
  include: { OperatingSector: true }  // ✅ PascalCase
});

Error 2: TypeError: Cannot read property 'name' of undefined

❌ // API returns { OperatingSector: {...} }
// Frontend reads room.operatingSector.name  // undefined

✅ // Map before returning
const mapped = rooms.map(room => ({
  ...room,
  operatingSector: room.OperatingSector
}));
return NextResponse.json(mapped);

Error 3: PrismaClientValidationError: Missing required field

❌ await prisma.site.create({
  data: { name: 'Test' }  // ❌ Missing id and updatedAt
});

✅ await prisma.site.create({
  data: {
    id: `site-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
    name: 'Test',
    updatedAt: new Date()
  }
});

📍 Progressive Resources

For complete detailed mappings:

  • resources/relations-table.md - All 121 models with relations
  • resources/required-fields.md - Complete list of required fields
  • resources/mapping-patterns.md - Advanced mapping examples

🔗 References

APIs with Complete Mapping

  • src/app/api/admin/sites-order/route.ts (GET:7-37, POST:98-111)
  • src/app/api/admin/sectors-order/route.ts (GET:12-43, POST:107-119)
  • src/app/api/admin/rooms-order/route.ts (GET:19-38, POST:106-122)

Services with Complex Relations

  • src/services/equity/EquityCounterService.ts (Assignment + User)
  • src/modules/assignments/services/AssignmentService.ts (Multi-relations)

Last Update: 27 October 2025 Next Review: After next major migration