💻 Développement

dev-prisma-expert

Expert Prisma ORM pour la conception de schémas, les migrations, l'optimisation de requêtes, la modélisation de relations et les opérations base de données.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-prisma-expert --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-prisma-expert -Launch"

🚀 Déjà installé ?

claude "/dev-prisma-expert"

Ou tapez /dev-prisma-expert dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

prismaschema prismamigration prismaprisma clientrequête prisma lenterelation prisma

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-prisma-expert ~/.claude/skills/

Payload du plugin : skills/dev-prisma-expert · source éditable : dev-skills/prisma-expert

📖 Manuel

Expert Prisma ORM

Workflow de diagnostic

  1. Catégoriser : schéma · migration · requête lente · connexion · transaction
  2. Valider l'état actuel : npx prisma validate + npx prisma migrate status
  3. Identifier le root cause : logs SQL (log: ['query']), EXPLAIN ANALYZE, état drift
  4. Appliquer la correction : minimal d'abord, vérifier l'impact
  5. Valider : relancer les commandes de diagnostic, tests d'intégration

Conception de schéma

Modèle canonique

model User {
  id        String   @id @default(cuid())   // ou uuid() selon besoin
  email     String   @unique
  role      Role     @default(USER)
  posts     Post[]   @relation("UserPosts")
  profile   Profile? @relation("UserProfile")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([email])
  @@index([role, createdAt])
  @@map("users")
}

enum Role { USER ADMIN MODERATOR }

model Post {
  id        String   @id @default(cuid())
  title     String
  status    PostStatus @default(DRAFT)
  authorId  String
  author    User     @relation("UserPosts", fields: [authorId], references: [id], onDelete: Cascade)

  @@index([authorId])
  @@index([status, createdAt])
  @@map("posts")
}

Many-to-Many explicite (toujours préférer)

// EVITER : relation implicite (perte de flexibilité)
model Post { tags Tag[] }
model Tag  { posts Post[] }

// FAIRE : table de jointure explicite
model PostTag {
  postId    String
  tagId     String
  addedAt   DateTime @default(now())
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  tag       Tag      @relation(fields: [tagId],  references: [id], onDelete: Cascade)

  @@id([postId, tagId])
  @@map("post_tags")
}

Critères de choix @id

CasChoix
Public, URL-friendlycuid() ou uuid()
Performance max (write-heavy)Int @id @default(autoincrement())
UUID v7 (ordre temporel)uuid() + trigger ou app-generated
Clé composée@@id([a, b])

Checklist schéma


Migrations

Environnements

# Développement — génère + applique + regénère le client
npx prisma migrate dev --name add_user_role

# CI/Staging — vérifier sans appliquer
npx prisma migrate diff \
  --from-schema-datasource prisma/schema.prisma \
  --to-schema-datamodel prisma/schema.prisma

# Production — UNIQUEMENT cette commande (jamais migrate dev)
npx prisma migrate deploy

# Résoudre une migration bloquée en prod
npx prisma migrate resolve --applied  "20240601_add_user_role"
npx prisma migrate resolve --rolled-back "20240601_add_user_role"

Drift de schéma

# Détecter un drift (DB modifiée hors Prisma)
npx prisma migrate diff \
  --from-schema-datasource prisma/schema.prisma \
  --to-migrations ./prisma/migrations

# Introspect pour récupérer l'état réel
npx prisma db pull

Migration destructive — procédure safe

# 1. Créer la migration sans l'appliquer
npx prisma migrate dev --name rename_col --create-only

# 2. Editer le SQL généré manuellement :
#    - Copier les données avant DROP
#    - Utiliser ADD COLUMN + UPDATE + DROP COLUMN en 2 déploiements

# 3. Appliquer
npx prisma migrate dev

Optimisation des requêtes

Problème N+1 — recette complète

// MAUVAIS : N+1 queries
const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}

// BON : include (charge tout)
const users = await prisma.user.findMany({ include: { posts: true } });

// MIEUX : select ciblé (moins de données réseau)
const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: { select: { id: true, title: true }, take: 5 }
  }
});

// OPTIMAL pour pagination + count : transaction parallèle
const [items, total] = await prisma.$transaction([
  prisma.post.findMany({ where, skip, take, orderBy }),
  prisma.post.count({ where }),
]);

Critère select vs include

$queryRaw — quand et comment

// Utiliser $queryRaw pour : agrégations complexes, WINDOW functions, requêtes non supportées
import { Prisma } from '@prisma/client';

const stats = await prisma.$queryRaw<{ userId: string; count: bigint }[]>`
  SELECT author_id as "userId", COUNT(*) as count
  FROM posts
  WHERE created_at > ${new Date('2025-01-01')}
  GROUP BY author_id
  HAVING COUNT(*) > 10
`;

// Toujours utiliser les template literals Prisma.sql (pas de string concat — injection risk)
const safeQuery = await prisma.$queryRaw(
  Prisma.sql`SELECT * FROM users WHERE id = ${userId}`
);

Activer les logs SQL en dev

const prisma = new PrismaClient({
  log: [
    { level: 'query', emit: 'event' },
    { level: 'warn',  emit: 'stdout' },
  ],
});
prisma.$on('query', (e) => {
  console.log(`Query: ${e.query} — ${e.duration}ms`);
});

Gestion des connexions

Singleton (Node.js / Next.js)

import { PrismaClient } from '@prisma/client';

const globalForPrisma = global as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'warn'] : ['warn'],
  });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

Serverless / Edge (Vercel, Cloudflare)

// Utiliser Prisma Accelerate ou connection pooling externe (PgBouncer)
// DATABASE_URL=prisma://accelerate.prisma-data.net/?api_key=...

import { PrismaClient } from '@prisma/client/edge';
import { withAccelerate } from '@prisma/extension-accelerate';

const prisma = new PrismaClient().$extends(withAccelerate());

// Avec cache Accelerate
const user = await prisma.user.findUnique({
  where: { id },
  cacheStrategy: { ttl: 60 },  // 60 secondes
});

Pool de connexions — valeurs recommandées

# connection_limit = (nb_CPU * 2) + 1 en général
DATABASE_URL="postgresql://user:pass@host/db?connection_limit=10&pool_timeout=15"

Transactions

// Transaction séquentielle (simple, atomic)
const [user, account] = await prisma.$transaction([
  prisma.user.create({ data: userData }),
  prisma.account.create({ data: accountData }),
]);

// Transaction interactive (logique conditionnelle)
const result = await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: userData });
  if (!user) throw new Error('User creation failed');  // rollback auto

  await tx.auditLog.create({ data: { action: 'USER_CREATED', userId: user.id } });
  return user;
}, {
  maxWait: 5000,   // attente de slot de connexion
  timeout: 10000,  // durée max de la transaction
  isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
});

Pièges et anti-patterns

Anti-patternProblèmeCorrection
migrate dev en productionRéinitialise la DB si driftUtiliser migrate deploy
include sans where sur relation largeCharge tout en mémoireAjouter take + where
Many-to-many impliciteImpossible d'ajouter des champs à la relationTable de jointure explicite
$queryRaw avec concaténation stringInjection SQLTemplate literals Prisma.sql
Instancier PrismaClient dans chaque handlerÉpuisement du poolSingleton global
Omettre onDeleteErreur FK en cascade ou orphelinsDéfinir Cascade / SetNull
Index manquant sur FKFull table scan à chaque JOIN@@index([foreignKeyField])
Enum modifié via db push en prodRisque de perte de donnéesMigration SQL explicite

Commandes essentielles

npx prisma generate          # Régénérer le client après changement schéma
npx prisma validate          # Valider le schéma
npx prisma format            # Formater schema.prisma
npx prisma migrate status    # État des migrations
npx prisma studio            # UI graphique pour explorer la DB
npx prisma db push           # Appliquer schéma sans migration (proto uniquement)
npx prisma db seed           # Exécuter le seed (prisma.seed dans package.json)