NestJS en TypeORM in 2026: Migraties, Relaties en Interviewvragen

NestJS TypeORM integratie met TypeORM 1.1 migraties, entity-relaties, repository patterns en veelgestelde interviewvragen voor backend-ontwikkelaars.

NestJS en TypeORM in 2026: Migraties, Relaties en Interviewvragen

NestJS TypeORM blijft een van de beproefde ORM-integraties voor enterprise Node.js backends. Met TypeORM versie 1.0 in mei 2026, gevolgd door de 1.1.x patchserie die verbindingsbeheer en relatiepersistentie verbetert, en NestJS 12 dat de ESM-migratie voltooit, biedt de stack een stabiele basis voor productieapplicaties. Deze gids behandelt de essentiele patronen voor elke backend-ontwikkelaar: migraties, entity-relaties, het repository-pattern en de bijbehorende interviewvragen.

NestJS 12 en TypeORM 1.1

NestJS 12 levert core-pakketten als ESM en vereist Node.js 20.19+ of 22.12+. Het @nestjs/typeorm v12 pakket volgt dezelfde aanpak. TypeORM 1.1.2 repareert verbindingslekken in DataSource.initialize() en corrigeert many-to-many junction row persistentie wanneer relaties niet geladen zijn.

TypeORM-migraties instellen in NestJS 12

De eerste regel voor productiedatabases: gebruik nooit synchronize: true. Auto-sync vergelijkt entity-metadata met het live schema en past wijzigingen direct toe, wat kan leiden tot dataverlies bij het verwijderen van kolommen, typewijzigingen of hernoemde velden. Migraties bieden een versiebeheerd, controleerbaar en omkeerbaar alternatief.

TypeORM 1.x vereist een aparte DataSource-configuratiebestand voor de CLI, gescheiden van de NestJS-moduleconfiguratie. De CLI kan NestJS dependency injection niet oplossen, dus heeft het een zelfstandig entrypoint nodig.

src/config/typeorm.config.tstypescript
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,
});

De NestJS-moduleconfiguratie weerspiegelt deze instellingen maar gebruikt het TypeOrmModule.forRootAsync-pattern met dependency injection:

src/app.module.tstypescript
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<number>('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 vervangt de handmatige entities-array door automatisch elke entity te registreren die via TypeOrmModule.forFeature() in feature modules wordt geimporteerd.

Migratie-workflow: genereren, controleren, uitvoeren

TypeORM 1.x levert typeorm-ts-node-commonjs als aanbevolen CLI-runner, ter vervanging van de oudere ts-node ./node_modules/typeorm/cli-aanpak. Voeg deze scripts toe aan package.json:

package.json (scripts section)json
{
  "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"
}

Het generate-commando vergelijkt entity-metadata met de live database en produceert SQL-statements. Controleer de output altijd voor het committen. TypeORM detecteert kolomtoevoegingen, -verwijderingen, typewijzigingen, indexmodificaties en foreign key-wijzigingen, maar mist data-backfills, tabelhernoemingen (die verschijnen als een drop gevolgd door een create), en alles met database-extensies of triggers.

Controleer gegenereerde migraties

Een hernoemde kolom genereert een DROP + ADD in plaats van een ALTER RENAME. Dit vernietigt bestaande data. Inspecteer altijd de SQL in de up()-methode en pas handmatig aan waar nodig.

Entity-relaties: OneToMany, ManyToOne, ManyToMany

TypeORM entity-relaties mappen direct naar SQL foreign keys met decorators. Het begrijpen van de drie kernrelatietypen en hun opties is fundamenteel voor elke NestJS databasemodule.

Een User met meerdere Order-records demonstreert het meest voorkomende patroon:

src/entities/user.entity.tstypescript
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;
}
src/entities/order.entity.tstypescript
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;
}

De @ManyToOne-kant bezit de foreign key. @JoinColumn is optioneel op @ManyToOne (TypeORM leidt het af), maar expliciete naamgeving voorkomt verrassingen bij raw queries of migratie-diffs.

Voor many-to-many relaties maakt TypeORM automatisch een junction table:

src/entities/product.entity.tstypescript
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 moet op precies een kant van de relatie staan. Het definieert welke entity de junction table bezit en controleert de naam ervan.

Klaar om je Node.js / NestJS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Repository-pattern en custom repositories

Het NestJS module- en DI-systeem integreert TypeORM repositories via TypeOrmModule.forFeature(). Het injecteren van de standaard Repository<T> dekt standaard CRUD-operaties. Wanneer queries complex worden, kapselen custom repositories die logica in.

src/orders/orders.module.tstypescript
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 {}
src/orders/orders.service.tstypescript
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<Order>,
  ) {}

  async findByUser(userId: string): Promise<Order[]> {
    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<Order[]> {
    // 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();
  }
}

De find()-API behandelt eenvoudige queries. De QueryBuilder behandelt joins, subqueries, aggregaties en alles wat fijnmazige SQL-controle vereist. Beide in dezelfde service mixen is volkomen geldig: gebruik find() voor simpele lookups en QueryBuilder wanneer de query niet via de options-API uitgedrukt kan worden.

Transactiebeheer voor data-integriteit

Operaties die meerdere tabellen wijzigen hebben transacties nodig. TypeORM biedt twee benaderingen: de DataSource.transaction()-methode en de QueryRunner-API.

src/orders/orders.service.ts (additional method)typescript
async createOrderWithItems(
  userId: string,
  items: { productId: string; quantity: number }[],
  dataSource: DataSource,
): Promise<Order> {
  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);
  });
}

De callback-gebaseerde transaction()-methode commit automatisch bij succes en rolt terug bij elke gegooide fout. Elke databaseoperatie binnen de callback moet de meegegeven manager gebruiken in plaats van de geinjecteerde repository, anders draaien die operaties buiten de transactie.

QueryRunner vs transaction()

Gebruik DataSource.transaction() voor eenvoudige multi-step operaties. Schakel over naar QueryRunner wanneer handmatige controle over commit/rollback-punten of savepoints vereist is, zoals bij partiele rollbacks in batchverwerking.

Veelgestelde NestJS TypeORM interviewvragen

Deze vragen komen regelmatig voor in backend-ontwikkelaar interviews. Elk antwoord richt zich op de diepgang die verwacht wordt in een technische discussie op senior niveau.

Waarom mag synchronize: true nooit in productie worden gebruikt?

Auto-sync past destructieve schemawijzigingen toe zonder bevestiging. Het verwijderen van een kolom, wijzigen van een type of verwijderen van een index gebeurt direct op live data. Migraties bieden versiebeheer, code review en rollback-mogelijkheid. Het enige veilige gebruik van synchronize is lokale ontwikkeling met wegwerpdata.

Wat is het verschil tussen eager en lazy relaties?

Eager relaties laden automatisch mee bij elke query op de parent entity. Lazy relaties retourneren een Promise en voeren pas een aparte query uit wanneer ze benaderd worden. Eager loading veroorzaakt N+1-achtige overhead wanneer de gerelateerde data niet nodig is. Lazy loading stelt de kosten uit maar kan onverwachte queries diep in de call stack triggeren. De expliciete relations-optie in find() of leftJoinAndSelect in QueryBuilder geeft volledige controle over wat per query geladen wordt.

Hoe werkt cascade in TypeORM-relaties?

Het instellen van cascade: true op een relatie betekent dat het opslaan van de parent entity ook niet-opgeslagen child entities persisteert. cascade: ['insert', 'update'] beperkt het gedrag tot specifieke operaties. Cascades werken op ORM-niveau, niet op databaseniveau. Voor database-niveau cascade deletes, gebruik onDelete: 'CASCADE' op de @ManyToOne-decorator, die een ON DELETE CASCADE-constraint genereert in de migratie.

Wanneer moet een custom repository worden gebruikt in plaats van de standaard Repository<T>?

Custom repositories kapselen complexe querylogica in die niet in services thuishoort. Tekenen om een custom repository te extraheren: dezelfde QueryBuilder-keten verschijnt in meerdere services, de query bevat meerdere joins of subqueries, of de query vereist raw SQL. Simpele find()-aanroepen rechtvaardigen geen custom repository.

Voor meer NestJS-architectuurvragen, zie de complete NestJS guards en interceptors gids.

Klaar om je Node.js / NestJS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Sources

Belangrijke punten voor NestJS TypeORM productie-apps

  • Gebruik een apart DataSource-configuratiebestand voor de TypeORM CLI, onafhankelijk van de NestJS-moduleconfiguratie
  • Activeer nooit synchronize in productie; vertrouw op gegenereerde en gecontroleerde migraties voor alle schemawijzigingen
  • Plaats @ManyToOne op de entity die de foreign key bezit, en gebruik expliciete @JoinColumn-naamgeving voor duidelijkheid in raw queries en migratie-output
  • Kies tussen de find() options-API voor simpele queries en QueryBuilder voor complexe joins, aggregaties en conditionele logica
  • Wrap multi-table mutaties in transacties met DataSource.transaction(), en zorg dat elke operatie binnen de callback de transactionele manager gebruikt
  • Met TypeORM 1.1.x dat het API-oppervlak stabiliseert en NestJS 12 dat de ESM-migratie voltooit, blijft deze stack een sterke keuze voor enterprise backends die gestructureerde, type-safe databasetoegang vereisen

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in Node.js / NestJS?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 21 oktober 2026

Tags

#nestjs
#typeorm
#migrations
#database
#typescript
#backend

Delen

Gerelateerde artikelen