Ce qu'un ORM automatise vraiment
Entity, repository, migrations et SQL généré sous contrôle.
Objectifs
À la fin de cette leçon, vous saurez :
- situer l'ORM entre votre code et le driver ;
- définir entity, repository, migration ;
- garder le contrôle : voir le SQL généré.
🔗 Pour vous rafraîchir la mémoire : les bases TypeScript · CRUD en SQL · jointures SQL · transactions SQL
La couche de plus
NestJS
↓ objets et méthodes
ORM (TypeORM, Prisma...)
↓ SQL généré
driver pg
↓ requêtes
PostgreSQL
L'ORM traduit vos objets en lignes et inversement. Votre vanilla faisait déjà tout cela à la main — l'ORM supprime l'écriture répétitive du CRUD, pas la connaissance du SQL. C'est pour cela que ce chapitre arrive APRÈS les chapitres SQL/driver : on ne peut comprendre ce que l'ORM cache qu'en ayant écrit ces requêtes soi-même.
Entity : décrire une table en classe
@Entity()
export class Task {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 200 })
titre: string;
@Column({ default: false })
fait: boolean;
@ManyToOne(() => User, (u) => u.tasks)
user: User;
}
La classe EST le schéma : colonnes, types, relations déclarés une fois — et synchronisables vers la base via des migrations (fichiers versionnés qui transforment le schéma, exactement vos 001_xxx.sql mais générés).
Repository : le CRUD sans SQL
const repo = dataSource.getRepository(Task);
await repo.find({ where: { fait: false }, order: { id: 'ASC' } });
// SELECT * FROM task WHERE fait = false ORDER BY id ASC
await repo.save({ titre: 'Via ORM', user });
// INSERT ... RETURNING
await repo.update(id, { fait: true });
await repo.delete(id);
Chaque appel produit du SQL que vous savez lire — c'est le test de compétence : savoir prédire la requête générée.
Voir le SQL généré
// TypeORM : logger activé
dataSource = new DataSource({
logging: true, // chaque requête affichée
});
// Prisma : requêtes loggées aussi
Réflexe obligatoire pendant l'apprentissage : activer le log, comparer avec ce que vous auriez écrit. Quand un find avec relations génère trois requêtes au lieu d'un JOIN, vous comprenez pourquoi il faut parfois descendre d'un niveau (query builder ou SQL brut) — l'ORM ne remplace jamais le jugement.
Les transactions côté ORM
Même logique qu'au chapitre 20 (BEGIN/COMMIT/ROLLBACK), syntaxe différente. L'ORM fournit une callback transactionnelle : tout ce qui utilise le gestionnaire reçu em participe à LA transaction, et un throw déclenche le rollback automatiquement :
await dataSource.transaction(async (em) => {
const tache = em.create(Tache, { titre: "Nouvelle", fait: false });
await em.save(tache);
await em.decrement(Utilisateur, { id: userId }, "quota"); // autre écriture
// si l'une des lignes échoue → rollback des DEUX
});
Trois règles survivantes du chapitre 20 se retrouvent à l'identique :
- regrouper les écritures qui doivent réussir ensemble, rien d'autre ;
- ne jamais mettre d'appel externe lent (email, HTTP) dans la transaction ;
- le commit n'intervient qu'à la sortie sans erreur de la callback.
Vérifiez par vous-même : provoquez volontairement l'échec de la deuxième écriture et constatez que la première a été annulée.
Exercice
- Écrivez l'entity User + Task avec leur relation.
- Pour chaque appel repository, écrivez AVANT le SQL attendu, puis vérifiez au log.
- Quel cas poussera vers du SQL brut ? (indice : agrégats complexes)
Résumé
- Entity = schéma en classe ; repository = CRUD objet ; migrations = évolution versionnée.
- L'ORM vaut par le CRUD répétitif ; le SQL reste roi pour l'exotique.
- Logging systématique = rester propriétaire de ses requêtes.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 3. Statistiques (COUNT ... GROUP BY ... FILTER), fenêtres analytiques, CTE récursives, optimisations fines : le query builder devient verbeux et le SQL brut reste le bon outil. L'application Devmind utilise d'ailleurs du SQL paramétré direct dans ses repositories — choix assumé quand la maîtrise prime.