NestJS dan MongoDB di 2026: Mongoose, Agregasi, dan Pertanyaan Interview

Kuasai NestJS dengan MongoDB dan Mongoose 9. Pelajari desain schema, pipeline agregasi, dan persiapan interview teknis dengan contoh praktis.

Integrasi framework NestJS dengan database MongoDB menampilkan arsitektur kode dan pipeline agregasi

NestJS 12 yang dikombinasikan dengan MongoDB melalui Mongoose 9 menyediakan stack yang siap produksi untuk membangun backend Node.js yang skalabel. Panduan ini membahas pola desain schema, pipeline agregasi, dan pertanyaan interview yang membedakan kandidat senior dari junior.

Referensi Cepat

Mongoose 9.9.4 membutuhkan Node.js 18+ dan mendukung MongoDB 6.0 hingga 8.0. NestJS 12 menyediakan paket yang siap ESM namun tetap kompatibel dengan proyek CommonJS.

Konfigurasi Mongoose dalam Aplikasi NestJS 12

Paket @nestjs/mongoose mengintegrasikan Mongoose dengan dependency injection NestJS. Instalasi dependensi yang diperlukan:

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

Registrasi koneksi pada module root:

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

Method forRoot menerima semua opsi koneksi Mongoose. Pengaturan maxPoolSize mencegah kelelahan koneksi saat beban tinggi, masalah umum di lingkungan produksi.

Desain Schema dengan Decorator TypeScript

Schema Mongoose di NestJS menggunakan decorator dari @nestjs/mongoose. Setiap schema dipetakan ke koleksi MongoDB.

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

Decorator @Prop mendefinisikan constraint field. Pengaturan index: true pada field yang sering di-query meningkatkan performa baca dengan mengorbankan kecepatan tulis.

Wawasan Interview

Pewawancara sering menanyakan trade-off antara embedded document dan referensi. Embedded document cocok untuk data yang diakses bersamaan (profil pengguna + preferensi). Referensi cocok untuk data yang tumbuh tak terbatas atau membutuhkan query independen (pengguna + pesanan).

Pola Repository dengan Injectable Service

NestJS mendorong pemisahan logika database ke dalam service. Decorator @InjectModel menyediakan akses ke model Mongoose.

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

Pemanggilan .exec() mengembalikan Promise yang benar alih-alih objek Query Mongoose. Hal ini penting untuk perilaku async/await yang tepat dan stack trace error yang akurat.

Pipeline Agregasi untuk Query Kompleks

Agregasi MongoDB menangani pelaporan, analitik, dan transformasi data yang pada database SQL diselesaikan dengan join dan GROUP BY.

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

Pipeline agregasi memproses dokumen melalui tahapan secara berurutan. Setiap tahap mentransformasi output untuk tahap berikutnya. Tahap $match memfilter di awal untuk mengurangi dokumen yang diproses oleh tahap selanjutnya.

Siap menguasai wawancara Node.js / NestJS Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Transaksi untuk Operasi Multi-Dokumen

MongoDB 4.0+ mendukung transaksi ACID multi-dokumen. Gunakan transaksi ketika beberapa dokumen harus diperbarui secara atomik.

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

Wrapper session.withTransaction menangani commit dan rollback secara otomatis. Jika ada operasi yang throw error, seluruh transaksi dibatalkan.

Catatan Produksi

Transaksi membutuhkan replica set MongoDB atau cluster sharded. Instance MongoDB standalone tidak mendukung transaksi. Tier Atlas M0/M2/M5 sudah menyertakan replica set secara default.

Strategi Indexing untuk Performa Query

Index menentukan performa query. Tanpa index yang tepat, MongoDB melakukan scan seluruh koleksi.

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

Compound index mendukung query yang memfilter atau mengurutkan berdasarkan beberapa field. Index { category: 1, price: -1 } mengoptimalkan query seperti find({ category }).sort({ price: -1 }).

Pertanyaan Interview Umum tentang NestJS dan MongoDB

Interview teknis menguji pemahaman konseptual dan pengalaman praktis. Pertanyaan-pertanyaan ini sering muncul untuk posisi backend senior.

Q: Bagaimana Mongoose menangani connection pooling?

Mongoose memelihara connection pool secara internal. Opsi maxPoolSize (default: 100) membatasi koneksi bersamaan. Setiap operasi mengambil koneksi dari pool, mengeksekusi, dan mengembalikannya. Connection pooling menghindari overhead pembuatan koneksi TCP baru per query.

Q: Kapan sebaiknya menggunakan embedded document dibanding referensi?

Embed data yang saling terkait dan memiliki pertumbuhan terbatas. Alamat pengiriman pengguna (maksimal 5-10) cocok untuk di-embed. Item pesanan di-embed dalam dokumen pesanan. Referensi cocok untuk relasi tak terbatas: pesanan pengguna selama bertahun-tahun, atau produk dalam kategori. Aturannya: jika data dimuat bersama 90% waktu dan tetap di bawah 16MB, embed saja.

Q: Jelaskan stage $lookup dalam pipeline agregasi.

$lookup melakukan left outer join antar koleksi. Stage ini mencocokkan dokumen dari koleksi asing berdasarkan kesetaraan field atau pipeline kustom. Tidak seperti join SQL, $lookup dieksekusi selama agregasi dan dapat menyertakan filtering dan projection tambahan dalam join.

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

Q: Bagaimana menangani migrasi schema di MongoDB?

Schema MongoDB berkembang berbeda dari SQL. Strategi umum:

  • Tambahkan field baru dengan nilai default (backward compatible)
  • Jalankan script migrasi yang memperbarui dokumen yang ada secara batch
  • Gunakan versioning schema dengan field schemaVersion
  • Mongoose middleware (pre('save')) dapat mentransformasi dokumen saat write
Tips Interview

Kandidat senior menjelaskan trade-off. Junior hanya menyebutkan fitur. Ketika ditanya tentang embedded vs referensi, diskusikan pola query, batas ukuran dokumen (16MB), dan frekuensi update, bukan hanya "tergantung".

Pola Error Handling dan Validasi

Validasi Mongoose berjalan sebelum operasi save. Validator kustom menangani logika bisnis.

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

Middleware pre('save') berjalan sebelum setiap operasi save. Gunakan untuk field yang dihitung, audit logging, atau cascading update.

Monitoring Performa dengan explain()

Method explain() mengungkapkan rencana eksekusi query. Gunakan untuk mengidentifikasi index yang hilang dan query yang lambat.

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
}

Rasio totalDocsExamined terhadap nReturned yang mendekati 1 menunjukkan penggunaan index yang efisien. Rasio tinggi menandakan index yang hilang atau query yang tidak selektif.

Poin Penting untuk Pengembangan NestJS MongoDB

  • Konfigurasi maxPoolSize berdasarkan konkurensi yang diharapkan, default 100 cocok untuk sebagian besar workload
  • Gunakan HydratedDocument<T> untuk typing TypeScript yang tepat pada dokumen Mongoose
  • Panggil .exec() pada query untuk mendapatkan Promise native dengan stack trace yang akurat
  • Letakkan tahap $match di awal pipeline agregasi untuk mengurangi dokumen yang diproses
  • Transaksi membutuhkan replica set, verifikasi topologi deployment sebelum mengandalkannya
  • Buat compound index yang sesuai dengan pola query: field filter dulu, field sort kedua
  • Embed dokumen dengan pertumbuhan terbatas, referensikan dokumen yang tumbuh tak terbatas
  • Monitor performa query dengan explain('executionStats') selama development

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Node.js / NestJS?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 28 Agustus 2026

Tag

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

Bagikan

Artikel terkait