2026'da NestJS ve TypeORM: Migration'lar, İlişkiler ve Mülakat Soruları

TypeORM 1.1 migration'ları, entity ilişkileri, repository pattern'leri ve backend geliştiriciler için yaygın mülakat sorularıyla NestJS TypeORM entegrasyonunda ustalaşın.

2026'da NestJS ve TypeORM: Migration'lar, İlişkiler ve Mülakat Soruları

NestJS TypeORM, kurumsal Node.js backend'leri için en iyi test edilmiş ORM entegrasyonlarından biri olmaya devam ediyor. TypeORM'un Mayıs 2026'da 1.0 sürümüne ulaşması, ardından bağlantı yönetimi ve ilişki kalıcılığını düzelten 1.1.x yama serisi ve NestJS 12'nin ESM geçişini tamamlamasıyla, bu stack üretim uygulamaları için olgun ve kararlı bir temel haline geldi. Bu rehber, her backend geliştiricinin ustalaşması gereken temel kalıpları kapsıyor: migration'lar, entity ilişkileri, repository pattern ve bunlarla ilgili mülakat soruları.

NestJS 12 ve TypeORM 1.1

NestJS 12, temel paketleri ESM olarak sunuyor ve Node.js 20.19+ veya 22.12+ gerektiriyor. @nestjs/typeorm v12 paketi de aynı modeli takip ediyor. TypeORM 1.1.2, DataSource.initialize() içindeki bağlantı sızıntılarını düzeltiyor ve ilişkiler yüklenmediğinde many-to-many junction satır kalıcılığını düzeltiyor.

NestJS 12'de TypeORM Migration'larını Yapılandırma

Üretim veritabanları için ilk kural: asla synchronize: true kullanmamak. Auto-sync, entity metadata'sını canlı şemayla karşılaştırır ve değişiklikleri doğrudan uygular; bu da sütun silme, tip değişikliği veya alan yeniden adlandırma durumlarında veri kaybı riski taşır. Migration'lar sürümlenmiş, incelenebilir ve geri alınabilir bir alternatif sunar.

TypeORM 1.x, CLI için NestJS modül yapılandırmasından ayrı, özel bir DataSource yapılandırma dosyası gerektirir. CLI, NestJS bağımlılık enjeksiyonunu çözemediğinden bağımsız bir giriş noktasına ihtiyaç duyar.

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,
});

NestJS modül yapılandırması bu ayarları yansıtır ancak bağımlılık enjeksiyonlu TypeOrmModule.forRootAsync pattern'ini kullanır:

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, feature modüllerinde TypeOrmModule.forFeature() ile import edilen her entity'yi otomatik olarak kaydederek manuel entities dizisinin yerini alır.

Migration İş Akışı: Oluştur, İncele, Çalıştır

TypeORM 1.x, eski ts-node ./node_modules/typeorm/cli yaklaşımının yerini alan typeorm-ts-node-commonjs'i önerilen CLI runner olarak sunar. Bu script'leri package.json'a ekleyin:

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"
}

generate komutu entity metadata'sını canlı veritabanıyla karşılaştırır ve SQL ifadeleri üretir. Çıktıyı commit etmeden önce her zaman inceleyin. TypeORM sütun ekleme, silme, tip değişiklikleri, indeks değişiklikleri ve foreign key değişikliklerini algılar, ancak veri backfill'lerini, tablo yeniden adlandırmalarını (drop ve create olarak görünür) ve veritabanı uzantıları veya trigger'larla ilgili herhangi bir şeyi kaçırır.

Her oluşturulan migration'ı inceleyin

Yeniden adlandırılan bir sütun, ALTER RENAME yerine DROP + ADD üretir. Bu mevcut verileri yok eder. up() metodundaki SQL'i her zaman inceleyin ve gerektiğinde manuel olarak düzeltin.

Entity İlişkileri: OneToMany, ManyToOne, ManyToMany

TypeORM entity ilişkileri, decorator'lar kullanarak doğrudan SQL foreign key'lerine eşlenir. Üç temel ilişki türünü ve seçeneklerini anlamak, her NestJS veritabanı modülü için temeldir.

Birden fazla Order kaydına sahip bir User en yaygın pattern'i gösterir:

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;
}

@ManyToOne tarafı foreign key'in sahibidir. @JoinColumn, @ManyToOne için isteğe bağlıdır (TypeORM çıkarır), ancak açık isimlendirme raw query'lerde veya migration diff'lerinde sürprizleri önler.

Many-to-many ilişkiler için TypeORM otomatik olarak bir junction tablo oluşturur:

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 ilişkinin tam olarak bir tarafında görünmelidir. Hangi entity'nin junction tablosuna sahip olduğunu tanımlar ve ismini kontrol eder.

Node.js / NestJS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Repository Pattern ve Custom Repository'ler

NestJS modül ve DI sistemi, TypeORM repository'lerini TypeOrmModule.forFeature() aracılığıyla entegre eder. Varsayılan Repository<T>'yi enjekte etmek standart CRUD işlemlerini kapsar. Sorgular karmaşıklaştığında, custom repository'ler bu mantığı kapsüller.

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();
  }
}

find() API'si basit sorguları yönetir. QueryBuilder join'leri, alt sorguları, aggregation'ları ve hassas SQL kontrolü gerektiren her şeyi yönetir. Her ikisini de aynı servis içinde kullanmak tamamen geçerlidir: basit aramalar için find(), sorgu seçenekler API'si ile ifade edilemediğinde QueryBuilder kullanın.

Veri Bütünlüğü için Transaction Yönetimi

Birden fazla tabloyu değiştiren işlemler transaction gerektirir. TypeORM iki yaklaşım sunar: DataSource.transaction() metodu ve 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);
  });
}

Callback tabanlı transaction() metodu başarıda otomatik olarak commit eder ve herhangi bir hata fırlatıldığında rollback yapar. Callback içindeki her veritabanı işlemi, enjekte edilen repository yerine sağlanan manager'ı kullanmalıdır; aksi takdirde bu işlemler transaction dışında çalışır.

QueryRunner vs transaction()

Basit çok adımlı işlemler için DataSource.transaction() kullanın. Commit/rollback noktaları veya savepoint'ler üzerinde manuel kontrol gerektiğinde, örneğin toplu işlemede kısmi rollback'ler için QueryRunner'a geçin.

Yaygın NestJS TypeORM Mülakat Soruları

Bu sorular backend geliştirici mülakatlarında sıkça karşımıza çıkar. Her yanıt, senior seviye teknik tartışmada beklenen derinliği hedefler.

synchronize: true neden üretimde asla kullanılmamalıdır?

Auto-sync, onay olmadan yıkıcı şema değişikliklerini uygular. Bir sütunu silmek, bir tipi değiştirmek veya bir indeksi kaldırmak canlı verilere anında uygulanır. Migration'lar sürüm kontrolü, kod inceleme ve rollback yeteneği sağlar. synchronize'ın tek güvenli kullanımı atılabilir verilerle yerel geliştirmedir.

eager ve lazy ilişkiler arasındaki fark nedir?

Eager ilişkiler parent entity üzerindeki her sorguyla otomatik olarak yüklenir. Lazy ilişkiler bir Promise döndürür ve yalnızca erişildiğinde ayrı bir sorgu çalıştırır. Eager loading, ilişkili veriler gerekmediğinde N+1 tarzı overhead'e neden olur. Lazy loading maliyeti erteler ancak çağrı yığınının derinliklerinde beklenmeyen sorgular tetikleyebilir. find()'daki açık relations seçeneği veya QueryBuilder'daki leftJoinAndSelect, sorgu başına neyin yükleneceği üzerinde tam kontrol sağlar.

TypeORM ilişkilerinde cascade nasıl çalışır?

Bir ilişkide cascade: true ayarlamak, parent entity'yi kaydetmenin kaydedilmemiş child entity'leri de kaydetmesi anlamına gelir. cascade: ['insert', 'update'] davranışı belirli işlemlerle sınırlar. Cascade'ler veritabanı seviyesinde değil ORM seviyesinde çalışır. Veritabanı seviyesinde cascade silme için @ManyToOne decorator'ında onDelete: 'CASCADE' kullanın; bu migration'da ON DELETE CASCADE constraint'i oluşturur.

Custom repository, varsayılan Repository<T> yerine ne zaman kullanılmalıdır?

Custom repository'ler servislere ait olmayan karmaşık sorgu mantığını kapsüller. Custom repository çıkarılması gerektiğinin işaretleri: aynı QueryBuilder zinciri birden fazla serviste görünüyor, sorgu birden fazla join veya alt sorgu içeriyor, veya sorgu raw SQL gerektiriyor. Basit find() çağrıları custom repository'yi haklı çıkarmaz.

Daha fazla NestJS mimari sorusu için, NestJS guards ve interceptors rehberine bakın.

Node.js / NestJS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Kaynaklar

NestJS TypeORM Üretim Uygulamaları için Temel Çıkarımlar

  • TypeORM CLI için NestJS modül yapılandırmasından bağımsız, ayrı bir DataSource yapılandırma dosyası kullanın
  • Üretim ortamlarında asla synchronize etkinleştirmeyin; tüm şema değişiklikleri için oluşturulan ve incelenen migration'lara güvenin
  • @ManyToOne'ı foreign key'e sahip entity'ye yerleştirin ve raw query'lerde ve migration çıktısında netlik için açık @JoinColumn isimlendirmesi kullanın
  • Basit sorgular için find() seçenekler API'si ile karmaşık join'ler, aggregation'lar ve koşullu mantık için QueryBuilder arasında seçim yapın
  • Çoklu tablo mutasyonlarını DataSource.transaction() kullanarak transaction'lara sarın ve callback içindeki her işlemin transactional manager'ı kullandığından emin olun
  • TypeORM 1.1.x'in API yüzeyini stabilize etmesi ve NestJS 12'nin ESM geçişini tamamlamasıyla, bu stack yapılandırılmış, tip güvenli veritabanı erişimi gerektiren kurumsal backend'ler için güçlü bir seçenek olmaya devam ediyor

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Günün meydan okuması

Node.js / NestJS kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

21 Ekim 2026 tarihinde güncellendi

Etiketler

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

Paylaş

İlgili makaleler