Prisma vs TypeORM : retour d'expérience sur 8 projets NestJS

12/03/2026
On a utilisé TypeORM sur 7 projets NestJS et Prisma sur 1. Voici ce qu'on en retient — pas de la théorie, du vécu.
Le contexte
Quand on a commencé à construire des API NestJS en 2021, TypeORM était le choix par défaut. Intégration native avec NestJS, documentation correcte, communauté active. Puis Prisma est arrivé avec une approche radicalement différente : schema-first, type-safe par design, et une DX qui promettait de changer la donne.
On a basculé un projet sur Prisma pour voir. Voici ce qu'on a appris.
Définition des modèles : décorateurs vs schema
TypeORM définit les modèles avec des décorateurs TypeScript. C'est verbeux mais lisible :
@Entity()
export class Appointment extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Workspace, (ws) => ws.appointments)
@JoinColumn()
workspace: Workspace;
@ManyToOne(() => Project, (p) => p.appointments)
@JoinColumn()
project: Project;
}
Prisma utilise un fichier .prisma dédié, plus concis :
model Relationship {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
sponsor User? @relation("Sponsor", fields: [sponsorId], references: [id])
downline User? @relation("Downline", fields: [downlineId], references: [id])
sponsorId String?
downlineId String?
status String @default("PENDING")
}
Notre tip : le schema Prisma est plus rapide à lire et à modifier. Mais les décorateurs TypeORM permettent de garder la logique ORM avec le code TypeScript — pas de fichier séparé à synchroniser mentalement.
Typage : là où Prisma écrase
Le client Prisma génère des types à partir du schema. Quand on écrit prisma.relationship.findMany(), l'autocomplétion connaît exactement les champs, les relations, et les filtres possibles. C'est du type-safety de bout en bout, sans effort.
Avec TypeORM, le typage existe mais il est plus lâche. Les relations dans un find() ne sont pas vérifiées à la compilation — on peut demander une relation qui n'existe pas et ne le découvrir qu'au runtime.
Notre tip : sur un projet avec beaucoup de relations complexes (self-referencing, many-to-many), le typage Prisma évite des bugs qu'on ne découvrirait qu'en prod avec TypeORM.
Soft deletes : avantage TypeORM
TypeORM gère les soft deletes nativement avec @DeleteDateColumn() :
export class BaseEntity {
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt?: Date;
}
Un softDelete() remplit le champ, et tous les find() excluent automatiquement les entités supprimées. Transparent.
Prisma n'a pas de soft delete natif. On ajoute un champ deletedAt DateTime? et on doit filtrer manuellement partout avec where: { deletedAt: null }. Sur un projet avec 15+ entités, c'est une source d'erreurs.
Notre tip : si le soft delete est central dans votre logique (audit trail, RGPD, corbeille), TypeORM simplifie la vie. Avec Prisma, prévoyez un middleware Prisma pour automatiser le filtrage.
Migrations : deux philosophies
TypeORM peut auto-synchroniser le schéma en dev (synchronize: true). Pratique mais dangereux — on l'a désactivé en prod dès le premier jour. Les migrations manuelles fonctionnent, mais on a accumulé 86 fichiers de migration sur un projet. Le tooling est correct sans plus.
Prisma a un workflow migration plus propre : prisma migrate dev crée une migration à partir du diff du schema. C'est plus prévisible et le fichier SQL généré est lisible. Par contre, les migrations custom (données à migrer, pas juste le schéma) nécessitent de sortir du workflow standard.
Notre tip : Prisma gagne sur les migrations de schéma. TypeORM gagne quand on a besoin de migrations de données complexes.
Relations self-referencing : Prisma gagne
Sur le projet MLM (réseau de parrainage), on avait une relation self-referencing : un utilisateur sponsorise d'autres utilisateurs, qui sponsorisent d'autres utilisateurs. Avec Prisma, c'est déclaratif et clair grâce aux relations nommées (@relation("Sponsor"), @relation("Downline")).
Avec TypeORM, les self-referencing many-to-many nécessitent plus de configuration et le eager loading devient vite un piège de performance si on ne fait pas attention.
Performance : pas de différence notable
Sur nos volumes (quelques milliers à quelques dizaines de milliers de lignes), on n'a pas mesuré de différence significative. Les deux génèrent du SQL propre. Les problèmes de performance viennent toujours du même endroit : des N+1 queries qu'on n'a pas anticipées.
Notre verdict
| Critère | TypeORM | Prisma |
|---|---|---|
| DX / autocomplétion | Correct | Excellent |
| Soft deletes | Natif | Manuel |
| Migrations schema | Correct | Meilleur |
| Migrations data | Flexible | Limité |
| Self-referencing | Verbeux | Clair |
| Écosystème NestJS | Natif | Plugin |
| Courbe d'apprentissage | Progressive | Rapide |
Si on démarrait un nouveau projet NestJS aujourd'hui : Prisma pour les projets avec beaucoup de relations complexes et une équipe qui valorise la DX. TypeORM pour les projets qui ont besoin de soft deletes natifs, de migrations de données flexibles, ou qui veulent rester dans l'écosystème NestJS sans plugin supplémentaire.
En pratique, les deux font le job. Le choix dépend plus de l'équipe que de la technologie.
Voir aussi : UUID + auto-increment : pourquoi on utilise les deux — un pattern TypeORM qu'on utilise sur les projets à fort trafic.
Stack concernée : NestJS, PostgreSQL, TypeORM 0.2-0.3, Prisma 5
