2026'da NestJS ve MongoDB: Mongoose, Aggregation'lar ve Mülakat Soruları

NestJS ile MongoDB ve Mongoose 9 konusunda uzmanlaşın. Şema tasarım kalıplarını, aggregation pipeline'larını öğrenin ve pratik örneklerle teknik mülakatlarına hazırlanın.

NestJS framework'ü ile MongoDB veritabanı entegrasyonu, kod mimarisi ve aggregation pipeline'ları gösteriliyor

NestJS 12, Mongoose 9 üzerinden MongoDB ile birleştirildiğinde, ölçeklenebilir Node.js backend'leri oluşturmak için üretime hazır bir stack sunmaktadır. Bu rehber, şema tasarım kalıplarını, aggregation pipeline'larını ve senior adayları junior'lardan ayıran mülakat sorularını kapsamaktadır.

Hızlı Referans

Mongoose 9.9.4, Node.js 18+ gerektirmekte ve MongoDB 6.0'dan 8.0'a kadar desteklemektedir. NestJS 12, ESM'ye hazır paketler sunmakta, ancak CommonJS projeleriyle geriye dönük uyumluluğunu korumaktadır.

NestJS 12 Uygulamasında Mongoose Kurulumu

@nestjs/mongoose paketi, Mongoose'u NestJS bağımlılık enjeksiyonu ile entegre eder. Gerekli bağımlılıklar yüklenmektedir:

bash
# Install Mongoose integration
npm install @nestjs/mongoose mongoose

Kök modülde bağlantı kaydedilir:

app.module.tstypescript
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot(process.env.MONGODB_URI, {
      // Connection pool size for production workloads
      maxPoolSize: 10,
      // Timeout after 10 seconds if connection fails
      serverSelectionTimeoutMS: 10000,
    }),
  ],
})
export class AppModule {}

forRoot metodu tüm Mongoose bağlantı seçeneklerini kabul etmektedir. maxPoolSize ayarı, yük altında bağlantı tükenmesini önler; bu yaygın bir üretim sorunudur.

TypeScript Decorator'ları ile Şema Tasarımı

NestJS'te Mongoose şemaları, @nestjs/mongoose'dan decorator'ları kullanmaktadır. Her şema bir MongoDB koleksiyonuna eşlenmektedir.

user.schema.tstypescript
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';

// Document type for TypeScript autocomplete
export type UserDocument = HydratedDocument<User>;

@Schema({
  timestamps: true, // Adds createdAt and updatedAt
  collection: 'users', // Explicit collection name
})
export class User {
  // MongoDB ObjectId, auto-generated
  _id: Types.ObjectId;

  @Prop({ required: true, unique: true, index: true })
  email: string;

  @Prop({ required: true })
  passwordHash: string;

  @Prop({ type: String, enum: ['admin', 'user', 'guest'], default: 'user' })
  role: string;

  @Prop({ type: [String], default: [] })
  permissions: string[];
}

export const UserSchema = SchemaFactory.createForClass(User);

@Prop decorator'ı alan kısıtlamalarını tanımlar. Sık sorgulanan alanlarda index: true ayarı, yazma işlemlerini yavaşlatma pahasına okuma performansını artırır.

Mülakat İpucu

Mülakatçılar, gömülü belgeler ile referanslar arasındaki trade-off hakkında soru sormaktadır. Gömülü belgeler, birlikte erişilen veriler için uygundur (kullanıcı profili + tercihler). Referanslar, sınırsız büyüyen veya bağımsız sorgular gerektiren veriler için uygundur (kullanıcı + siparişler).

Enjekte Edilebilir Servisler ile Repository Kalıbı

NestJS, veritabanı mantığının servisler içinde ayrılmasını teşvik etmektedir. @InjectModel decorator'ı, Mongoose modeline erişim sağlar.

user.service.tstypescript
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { User, UserDocument } from './user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,
  ) {}

  async findById(id: string): Promise<UserDocument> {
    // Validate ObjectId format before querying
    if (!Types.ObjectId.isValid(id)) {
      throw new NotFoundException('Invalid user ID format');
    }
    const user = await this.userModel.findById(id).exec();
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }
    return user;
  }

  async findByEmail(email: string): Promise<UserDocument | null> {
    // Case-insensitive email lookup
    return this.userModel.findOne({ 
      email: { $regex: new RegExp(`^${email}$`, 'i') } 
    }).exec();
  }

  async create(data: Partial<User>): Promise<UserDocument> {
    const user = new this.userModel(data);
    return user.save();
  }
}

.exec() çağrısı, Mongoose Query nesnesi yerine uygun bir Promise döndürmektedir. Bu, doğru async/await davranışı ve hata stack trace'leri için önemlidir.

Karmaşık Sorgular için Aggregation Pipeline'ları

MongoDB aggregation'ları, SQL veritabanlarının JOIN ve GROUP BY ile çözdüğü raporlama, analitik ve veri dönüşümlerini yönetmektedir.

analytics.service.tstypescript
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, PipelineStage } from 'mongoose';
import { Order, OrderDocument } from './order.schema';

@Injectable()
export class AnalyticsService {
  constructor(
    @InjectModel(Order.name) private orderModel: Model<OrderDocument>,
  ) {}

  async getRevenueByMonth(year: number): Promise<MonthlyRevenue[]> {
    const pipeline: PipelineStage[] = [
      // Stage 1: Filter orders by year
      {
        $match: {
          createdAt: {
            $gte: new Date(`${year}-01-01`),
            $lt: new Date(`${year + 1}-01-01`),
          },
          status: 'completed',
        },
      },
      // Stage 2: Group by month, sum revenue
      {
        $group: {
          _id: { $month: '$createdAt' },
          totalRevenue: { $sum: '$amount' },
          orderCount: { $sum: 1 },
          avgOrderValue: { $avg: '$amount' },
        },
      },
      // Stage 3: Sort by month ascending
      { $sort: { _id: 1 } },
      // Stage 4: Reshape output
      {
        $project: {
          _id: 0,
          month: '$_id',
          totalRevenue: { $round: ['$totalRevenue', 2] },
          orderCount: 1,
          avgOrderValue: { $round: ['$avgOrderValue', 2] },
        },
      },
    ];

    return this.orderModel.aggregate(pipeline).exec();
  }
}

Aggregation pipeline'ları, belgeleri aşamalar boyunca sırayla işlemektedir. Her aşama, bir sonraki aşama için çıktıyı dönüştürür. $match aşaması erken filtreleme yaparak sonraki aşamalar tarafından işlenen belge sayısını azaltır.

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.

Çoklu Belge İşlemleri için Transaction'lar

MongoDB 4.0+ çoklu belge ACID transaction'larını desteklemektedir. Birden fazla belgenin atomik olarak güncellenmesi gerektiğinde transaction'lar kullanılmalıdır.

transfer.service.tstypescript
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { Connection, Model, ClientSession } from 'mongoose';
import { Account, AccountDocument } from './account.schema';

@Injectable()
export class TransferService {
  constructor(
    @InjectConnection() private connection: Connection,
    @InjectModel(Account.name) private accountModel: Model<AccountDocument>,
  ) {}

  async transfer(
    fromId: string,
    toId: string,
    amount: number,
  ): Promise<void> {
    // Start a session for the transaction
    const session: ClientSession = await this.connection.startSession();
    
    try {
      await session.withTransaction(async () => {
        // Debit source account
        const source = await this.accountModel.findOneAndUpdate(
          { _id: fromId, balance: { $gte: amount } },
          { $inc: { balance: -amount } },
          { session, new: true },
        );
        
        if (!source) {
          throw new BadRequestException('Insufficient balance or account not found');
        }

        // Credit destination account
        const dest = await this.accountModel.findByIdAndUpdate(
          toId,
          { $inc: { balance: amount } },
          { session, new: true },
        );

        if (!dest) {
          throw new BadRequestException('Destination account not found');
        }
      });
    } finally {
      await session.endSession();
    }
  }
}

session.withTransaction wrapper'ı, commit ve rollback işlemlerini otomatik olarak yönetir. Herhangi bir işlem hata fırlatırsa, tüm transaction iptal edilir.

Üretim Notu

Transaction'lar MongoDB replica set veya sharded cluster gerektirmektedir. Bağımsız MongoDB instance'ları transaction'ları desteklememektedir. Atlas M0/M2/M5 katmanları varsayılan olarak replica set'ler içermektedir.

Sorgu Performansı için İndeksleme Stratejileri

İndeksler sorgu performansını belirler. Uygun indeksler olmadan MongoDB tüm koleksiyonları tarar.

product.schema.tstypescript
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';

export type ProductDocument = HydratedDocument<Product>;

@Schema({ timestamps: true })
export class Product {
  @Prop({ required: true, index: true })
  sku: string;

  @Prop({ required: true })
  name: string;

  @Prop({ required: true })
  category: string;

  @Prop({ required: true })
  price: number;

  @Prop({ default: true })
  inStock: boolean;
}

export const ProductSchema = SchemaFactory.createForClass(Product);

// Compound index for common query pattern
ProductSchema.index({ category: 1, price: -1 });

// Text index for search functionality
ProductSchema.index({ name: 'text', sku: 'text' });

Bileşik indeksler, birden fazla alana göre filtreleyen veya sıralayan sorguları destekler. { category: 1, price: -1 } indeksi, find({ category }).sort({ price: -1 }) gibi sorguları optimize eder.

NestJS ve MongoDB Hakkında Yaygın Mülakat Soruları

Teknik mülakatlar hem kavramsal anlayışı hem de pratik deneyimi test etmektedir. Bu sorular senior backend pozisyonlarında sıkça karşılaşılmaktadır.

S: Mongoose bağlantı pooling'ini nasıl yönetir?

Mongoose dahili olarak bir bağlantı havuzu sürdürmektedir. maxPoolSize seçeneği (varsayılan: 100) eşzamanlı bağlantıları sınırlar. Her işlem havuzdan bir bağlantı alır, çalıştırır ve geri döndürür. Bağlantı pooling, her sorgu için yeni TCP bağlantıları kurma yükünü ortadan kaldırır.

S: Referanslar yerine gömülü belgeler ne zaman kullanılmalıdır?

Birlikte ait olan ve sınırlı büyümesi olan verileri gömün. Kullanıcının teslimat adresleri (maksimum 5-10) iyi şekilde gömülür. Sipariş kalemleri sipariş belgesi içinde gömülür. Referanslar sınırsız ilişkiler için uygundur: kullanıcının yıllar içindeki siparişleri veya bir kategorideki ürünler. Kural: veriler zamanın %90'ında birlikte yükleniyorsa ve 16MB'ın altındaysa, gömün.

S: Aggregation pipeline'daki $lookup aşamasını açıklayın.

$lookup aşaması koleksiyonlar arasında left outer join gerçekleştirir. Alan eşitliğine veya özel bir pipeline'a dayalı olarak yabancı koleksiyondan belgeleri eşleştirir. SQL join'lerden farklı olarak, $lookup aggregation sırasında çalışır ve join içinde ek filtreleme ve projeksiyon içerebilir.

typescript
// Example: Orders with customer details
const pipeline: PipelineStage[] = [
  {
    $lookup: {
      from: 'customers',
      localField: 'customerId',
      foreignField: '_id',
      as: 'customer',
    },
  },
  { $unwind: '$customer' },
];

S: MongoDB'de şema migrasyonlarını nasıl yönetirsiniz?

MongoDB şemaları SQL'den farklı şekilde evrilir. Yaygın stratejiler:

  • Varsayılan değerlerle yeni alanlar ekleme (geriye dönük uyumlu)
  • Mevcut belgeleri partiler halinde güncelleyen migrasyon scriptleri çalıştırma
  • schemaVersion alanı ile şema versiyonlama kullanma
  • Mongoose middleware'i (pre('save')) yazma sırasında belgeleri dönüştürebilir
Mülakat İpucu

Senior adaylar trade-off'ları açıklar. Junior'lar özellikleri listeler. Gömülü vs referanslı belgeler sorulduğunda, sorgu kalıplarını, belge boyutu limitlerini (16MB) ve güncelleme sıklığını tartışın, sadece "duruma bağlı" demeyin.

Hata Yönetimi ve Validasyon Kalıpları

Mongoose validasyonu kaydetme işlemlerinden önce çalışır. Özel validator'lar iş mantığını yönetir.

order.schema.tstypescript
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';

export type OrderDocument = HydratedDocument<Order>;

@Schema({ timestamps: true })
export class Order {
  @Prop({
    required: true,
    validate: {
      validator: (v: number) => v > 0,
      message: 'Amount must be positive',
    },
  })
  amount: number;

  @Prop({
    type: String,
    enum: ['pending', 'processing', 'completed', 'cancelled'],
    default: 'pending',
  })
  status: string;

  @Prop({ required: true })
  items: OrderItem[];
}

export const OrderSchema = SchemaFactory.createForClass(Order);

// Pre-save hook for computed fields
OrderSchema.pre('save', function (next) {
  // Recalculate total from items
  if (this.isModified('items')) {
    this.amount = this.items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0,
    );
  }
  next();
});

pre('save') middleware'i her kaydetme işleminden önce çalışır. Hesaplanan alanlar, denetim günlüğü veya kademeli güncellemeler için kullanılır.

explain() ile Performans İzleme

explain() metodu sorgu yürütme planlarını ortaya koyar. Eksik indeksleri ve yavaş sorguları tespit etmek için kullanılır.

typescript
// Debug query performance
async analyzeQuery(category: string): Promise<void> {
  const explanation = await this.productModel
    .find({ category, inStock: true })
    .sort({ price: -1 })
    .explain('executionStats');

  console.log('Documents examined:', explanation.executionStats.totalDocsExamined);
  console.log('Documents returned:', explanation.executionStats.nReturned);
  console.log('Execution time (ms):', explanation.executionStats.executionTimeMillis);
  // If totalDocsExamined >> nReturned, add an index
}

totalDocsExamined ile nReturned oranının 1'e yakın olması verimli indeks kullanımını gösterir. Yüksek oranlar eksik indeksleri veya seçici olmayan sorguları işaret eder.

NestJS MongoDB Geliştirme için Önemli Çıkarımlar

  • maxPoolSize'ı beklenen eşzamanlılığa göre yapılandırın, varsayılan 100 çoğu iş yükü için uygundur
  • Mongoose belgelerinin düzgün TypeScript tiplemesi için HydratedDocument<T> kullanın
  • Doğru stack trace'ler ile native Promise'lar almak için sorgularda .exec() çağırın
  • İşlenen belgeleri azaltmak için aggregation pipeline'larında $match aşamalarını erken yerleştirin
  • Transaction'lar replica set'ler gerektirir, güvenmeden önce dağıtım topolojisini doğrulayın
  • Sorgu kalıplarıyla eşleşen bileşik indeksler oluşturun: önce filtre alanları, sonra sıralama alanları
  • Sınırlı büyümesi olan belgeleri gömün, sınırsız büyüyen belgeleri referanslayın
  • Geliştirme sırasında explain('executionStats') ile sorgu performansını izleyin

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.

28 Ağustos 2026 tarihinde güncellendi

Etiketler

#nestjs
#mongodb
#mongoose
#nodejs
#backend
#interview

Paylaş

İlgili makaleler