# NestJS et TypeORM en 2026 : migrations, relations et questions d'entretien > Maîtriser l'intégration NestJS TypeORM avec les migrations de TypeORM 1.0, les relations entre entités, le pattern repository et les questions d'entretien courantes pour les développeurs backend. - Published: 2026-06-19 - Updated: 2026-06-19 - Author: SharpSkill - Tags: nestjs, typeorm, migrations, database, typescript, backend - Reading time: 9 min --- L'intégration NestJS TypeORM reste l'une des plus éprouvées pour les backends Node.js d'entreprise. Avec [TypeORM qui atteint la version 1.0](https://typeorm.io/blog/) en mai 2026 après presque une décennie de développement, et [NestJS 11](https://docs.nestjs.com/) qui livre Express v5 par défaut, cette stack est devenue une fondation stable pour les applications de production. Ce guide couvre les patterns essentiels que tout développeur backend doit maîtriser : les migrations, les relations entre entités, le pattern repository et les questions d'entretien qui les accompagnent. > **Le jalon TypeORM 1.0** > > TypeORM 1.0.0, publié le 19 mai 2026, modernise les prérequis de la plateforme vers ECMAScript 2023, supprime les API dépréciées et corrige l'ordre des migrations : les migrations en attente s'exécutent désormais avant la synchronisation du schéma lorsque les deux sont activées. ## Configurer les migrations TypeORM dans NestJS 11 Première règle des bases de données de production : ne jamais utiliser `synchronize: true`. La synchronisation automatique compare les métadonnées des entités avec le schéma en place et applique les changements directement, ce qui expose à une perte de données lors de suppressions de colonnes, de changements de type ou de renommages de champs. Les migrations offrent une alternative versionnée, relisible et réversible. TypeORM 1.0 exige un fichier de configuration `DataSource` dédié pour le CLI, distinct de la configuration du module NestJS. Le CLI ne peut pas résoudre l'injection de dépendances de NestJS, il lui faut donc un point d'entrée autonome. ```typescript // src/config/typeorm.config.ts import { DataSource } from 'typeorm'; import { config } from 'dotenv'; config(); // Load .env variables export default new DataSource({ type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT || '5432'), username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: ['src/**/*.entity.ts'], migrations: ['src/migrations/*.ts'], synchronize: false, }); ``` La configuration du module NestJS reprend ces réglages mais utilise le pattern `TypeOrmModule.forRootAsync` avec injection de dépendances : ```typescript // src/app.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'postgres', host: config.get('DB_HOST'), port: config.getOrThrow('DB_PORT'), username: config.get('DB_USER'), password: config.get('DB_PASSWORD'), database: config.get('DB_NAME'), autoLoadEntities: true, // Auto-register entities from feature modules synchronize: false, // Always false in production }), }), ], }) export class AppModule {} ``` `autoLoadEntities: true` remplace le tableau `entities` manuel en enregistrant automatiquement chaque entité importée via `TypeOrmModule.forFeature()` dans les modules de fonctionnalité. ## Le flux de migration : générer, relire, exécuter TypeORM 1.0 livre `typeorm-ts-node-commonjs` comme runner CLI recommandé, en remplacement de l'ancienne approche `ts-node ./node_modules/typeorm/cli`. Ajouter ces scripts au `package.json` : ```json // package.json (scripts section) { "migration:generate": "typeorm-ts-node-commonjs migration:generate src/migrations/$npm_config_name -d src/config/typeorm.config.ts", "migration:run": "typeorm-ts-node-commonjs migration:run -d src/config/typeorm.config.ts", "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/config/typeorm.config.ts" } ``` La commande de génération compare les métadonnées des entités à la base de données en place et produit des instructions SQL. Toujours relire la sortie avant de la committer. TypeORM détecte les ajouts de colonnes, les suppressions, les changements de type, les modifications d'index et les changements de clé étrangère, mais il manque les backfills de données, les renommages de tables (qui apparaissent comme une suppression suivie d'une création) et tout ce qui touche aux extensions ou aux triggers de la base. > **Relire chaque migration générée** > > Une colonne renommée génère un DROP + ADD au lieu d'un ALTER RENAME. Cela détruit les données existantes. Toujours inspecter le SQL dans la méthode `up()` et l'ajuster manuellement si nécessaire. ## Relations entre entités : OneToMany, ManyToOne, ManyToMany Les relations entre entités TypeORM se traduisent directement en clés étrangères SQL grâce aux décorateurs. Comprendre les trois types de relations fondamentaux et leurs options est essentiel pour tout [module base de données NestJS](/technologies/node-nestjs/interview-questions/database-typeorm). Un `User` avec plusieurs enregistrements `Order` illustre le pattern le plus courant : ```typescript // src/entities/user.entity.ts import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm'; import { Order } from './order.entity'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) email: string; @Column() name: string; @OneToMany(() => Order, (order) => order.user) orders: Order[]; // No DB column created here; relation lives on the Order side @CreateDateColumn() createdAt: Date; } ``` ```typescript // src/entities/order.entity.ts import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; import { User } from './user.entity'; @Entity('orders') export class Order { @PrimaryGeneratedColumn('uuid') id: string; @Column('decimal', { precision: 10, scale: 2 }) total: number; @Column({ default: 'pending' }) status: string; @ManyToOne(() => User, (user) => user.orders, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'user_id' }) // Explicit FK column name user: User; } ``` Le côté `@ManyToOne` détient la clé étrangère. `@JoinColumn` est optionnel sur `@ManyToOne` (TypeORM l'infère), mais un nommage explicite évite les surprises lorsque le nom de colonne compte pour des requêtes brutes ou des diffs de migration. Pour les relations many-to-many, TypeORM crée automatiquement une table de jonction : ```typescript // src/entities/product.entity.ts import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm'; import { Category } from './category.entity'; @Entity('products') export class Product { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: string; @Column('decimal', { precision: 10, scale: 2 }) price: number; @ManyToMany(() => Category, (category) => category.products) @JoinTable({ name: 'product_categories' }) // Owns the junction table categories: Category[]; } ``` `@JoinTable` doit apparaître sur un seul côté de la relation. Il définit quelle entité détient la table de jonction et contrôle son nom. ## Le pattern repository et les repositories personnalisés Le [système de modules et d'injection de dépendances de NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) intègre les repositories TypeORM via `TypeOrmModule.forFeature()`. Injecter le `Repository` par défaut couvre les opérations CRUD standard. Lorsque les requêtes se complexifient, les repositories personnalisés encapsulent cette logique. ```typescript // src/orders/orders.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Order } from '../entities/order.entity'; import { OrdersService } from './orders.service'; import { OrdersController } from './orders.controller'; @Module({ imports: [TypeOrmModule.forFeature([Order])], providers: [OrdersService], controllers: [OrdersController], }) export class OrdersModule {} ``` ```typescript // src/orders/orders.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Order } from '../entities/order.entity'; @Injectable() export class OrdersService { constructor( @InjectRepository(Order) private readonly orderRepo: Repository, ) {} async findByUser(userId: string): Promise { return this.orderRepo.find({ where: { user: { id: userId } }, relations: ['user'], // Eager-load the user relation order: { createdAt: 'DESC' }, // Most recent first }); } async findWithFilters(status: string, minTotal: number): Promise { // QueryBuilder for complex queries return this.orderRepo .createQueryBuilder('order') .leftJoinAndSelect('order.user', 'user') .where('order.status = :status', { status }) .andWhere('order.total >= :minTotal', { minTotal }) .orderBy('order.total', 'DESC') .getMany(); } } ``` L'API `find()` gère les requêtes simples. Le `QueryBuilder` gère les jointures, les sous-requêtes, les agrégations et tout ce qui exige un contrôle fin du SQL. Mélanger les deux dans un même service est parfaitement valide : utiliser `find()` pour les recherches simples et `QueryBuilder` lorsque la requête ne peut pas s'exprimer via l'API d'options. ## Gestion des transactions pour l'intégrité des données Les opérations qui modifient plusieurs tables ont besoin de transactions. TypeORM propose deux approches : la méthode `DataSource.transaction()` et l'API `QueryRunner`. ```typescript // src/orders/orders.service.ts (additional method) async createOrderWithItems( userId: string, items: { productId: string; quantity: number }[], dataSource: DataSource, ): Promise { return dataSource.transaction(async (manager) => { // All operations use the transactional manager const order = manager.create(Order, { user: { id: userId }, total: 0, status: 'pending', }); const savedOrder = await manager.save(order); let total = 0; for (const item of items) { const product = await manager.findOneByOrFail(Product, { id: item.productId }); total += product.price * item.quantity; await manager.save(OrderItem, { order: savedOrder, product, quantity: item.quantity, unitPrice: product.price, }); } savedOrder.total = total; return manager.save(savedOrder); }); } ``` La méthode `transaction()` à base de callback committe automatiquement en cas de succès et effectue un rollback dès qu'une erreur est levée. Chaque opération de base de données à l'intérieur du callback doit utiliser le `manager` fourni plutôt que le repository injecté, sinon ces opérations s'exécutent en dehors de la transaction. > **QueryRunner ou transaction() ?** > > Utiliser `DataSource.transaction()` pour les opérations multi-étapes simples. Passer à `QueryRunner` lorsqu'un contrôle manuel des points de commit/rollback ou des savepoints est nécessaire, comme les rollbacks partiels dans un traitement par lots. ## Questions d'entretien courantes sur NestJS TypeORM Ces questions reviennent fréquemment dans les entretiens de développeurs backend. Chaque réponse vise la profondeur attendue dans une discussion technique de niveau senior. **Pourquoi ne faut-il jamais utiliser `synchronize: true` en production ?** La synchronisation automatique applique des changements de schéma destructeurs sans confirmation. Supprimer une colonne, changer un type ou retirer un index se produit immédiatement sur des données en production. Les migrations apportent le versionnage, la relecture de code et la capacité de rollback. Le seul usage sûr de `synchronize` est le développement local avec des données jetables. **Quelle est la différence entre les relations `eager` et `lazy` ?** Les relations eager se chargent automatiquement à chaque requête sur l'entité parente. Les relations lazy renvoient une `Promise` et exécutent une requête séparée uniquement lors de l'accès. Le chargement eager engendre une surcharge de type N+1 lorsque les données liées ne sont pas nécessaires. Le chargement lazy diffère le coût mais peut déclencher des requêtes inattendues au fond de la pile d'appels. L'option explicite `relations` dans `find()` ou `leftJoinAndSelect` dans `QueryBuilder` donne un contrôle total sur ce qui est chargé par requête. **Comment fonctionne `cascade` dans les relations TypeORM ?** Définir `cascade: true` sur une relation signifie qu'enregistrer l'entité parente persiste aussi les entités enfants non enregistrées. `cascade: ['insert', 'update']` limite le comportement à des opérations spécifiques. Les cascades opèrent au niveau de l'ORM, pas au niveau de la base de données. Pour des suppressions en cascade au niveau base de données, utiliser `onDelete: 'CASCADE'` sur le décorateur `@ManyToOne`, qui génère une contrainte `ON DELETE CASCADE` dans la migration. **Quand faut-il utiliser un repository personnalisé plutôt que le `Repository` par défaut ?** Les repositories personnalisés encapsulent une logique de requête complexe qui n'a pas sa place dans les services. Les signes pour extraire un repository personnalisé : la même chaîne `QueryBuilder` apparaît dans plusieurs services, la requête implique plusieurs jointures ou sous-requêtes, ou la requête exige du SQL brut. De simples appels `find()` ne justifient pas un repository personnalisé. Pour davantage de questions sur l'architecture NestJS, voir le guide complet sur les [guards et interceptors NestJS](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture). ## Conclusion - Utiliser un **fichier de configuration `DataSource` distinct** pour le CLI TypeORM, indépendant de la configuration du module NestJS - **Ne jamais activer `synchronize` en production** ; s'appuyer sur des migrations générées et relues pour tous les changements de schéma - Placer `@ManyToOne` sur l'entité qui détient la clé étrangère et utiliser un nommage explicite via `@JoinColumn` pour plus de clarté dans les requêtes brutes et la sortie des migrations - Choisir entre l'API d'options `find()` pour les requêtes simples et `QueryBuilder` pour les jointures complexes, les agrégations et la logique conditionnelle - Encapsuler les mutations multi-tables dans des **transactions** via `DataSource.transaction()`, et veiller à ce que chaque opération dans le callback utilise le `manager` transactionnel - Avec TypeORM 1.0 qui stabilise la surface de son API et NestJS 11 qui mûrit son [architecture de modules](/technologies/node-nestjs), cette stack reste un choix solide pour les backends d'entreprise qui exigent un accès aux données structuré et typé --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/node-nestjs/nestjs-typeorm-migrations-relations-best-practices