prisma-relations-mapping
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.
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).
| name | prisma-relations-mapping |
|---|---|
| description | > |
| CRITICAL | Use 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
- Complete relation conversion table (PascalCase ↔ camelCase)
- API → Frontend mapping patterns
- Required fields without
@defaultper model - 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
_countto 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 relationsresources/required-fields.md- Complete list of required fieldsresources/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