WisarWisar
Dasturlash kitobi/8-QISM — NestJS24 daqiqa

8.13-bob: NestJS + MongoDB (Mongoose chuqur)

8-QISM — NestJS (chuqur) · 13-mavzu


1. Kirish va motivatsiya

Bot'ni 8.12-bob bildik. Endi NestJS'da MongoDB ni — eng mashhur NoSQL bazani — chuqur ko'ramiz. 6.2-6.3'da Mongoose'ni mustaqil o'rgandik (schema, model, query, populate, aggregation); 8.3'da DB ulanishni umumiy ko'rdik. Endi NestJS'da @nestjs/mongoose bilan — schema dekoratorlar (@Schema, @Prop), DI bilan model inject (@InjectModel), ref/populate, virtuallar, aggregation, transaction — to'liq, NestJS uslubida. SQL'ni (8.3, 8.4, ORM) bilganingizdan keyin, NoSQL'ni ham NestJS'da to'liq bilasiz — ikkala dunyoga tayyor.

NestJS Mongoose integratsiyasi — @nestjs/mongoose (Mongoose ustida). U schema'ni dekorator bilan yozishga imkon beradi: @Schema() (klass = schema), @Prop() (maydon). Bu — TypeScript klass (tur xavfsiz — 7) bir vaqtda Mongoose schema. Boilerplate kamayadi, o'qiladi. MongooseModule.forRootAsync (ulanish — 8.3), forFeature (model ro'yxati), @InjectModel (DI bilan model). Bu — 6.2-6.3'ning NestJS versiyasi (DI, dekorator, toza).

Bu bob: MongooseModule, schema dekoratorlar, @InjectModel, CRUD, ref/populate 6.3-bob, virtuallar, aggregation 6.3-bob, transaction, indekslar, hook'lar — chuqur. Bu bob 6.2-6.3 (Mongoose) ni NestJS arxitekturasiga bog'laydi. MongoDB — bot 8.12-bob, real-time, tez prototip uchun ideal NoSQL.

O'xshatish: SQL (8.3, 8.4) — qat'iy jadval/qatorlar (Excel — har ustun aniq). MongoDB — hujjatlar to'plami (papkadagi JSON fayllar — moslashuvchan). NestJS'da Mongoose schema — bu papkaning "qoidasi" (har hujjat qanday ko'rinishda). @Schema/@Prop — qoidani TypeScript klass sifatida yozish (tur + Mongoose bir joyda). populate — bog'langan hujjatni "olib kelish" (SQL JOIN'ning NoSQL muqobili — 6.3). NestJS — buni DI bilan tashkillaydi.

Nega muhim?

  • Eng mashhur NoSQL — moslashuvchan, tez 6.1-bob.
  • NestJS integratsiya — dekorator schema (tur xavfsiz, toza).
  • Bot/real-time — MongoDB ko'p ishlatiladi 8.12-bob.
  • To'liq stack — SQL 8.3-bob + NoSQL 8.13-bob — ikkala dunyo.

2. Nazariya — chuqur tushuntirish

2.1. MongooseModule sozlash (ulanish — 8.3)

ts
// app.module.ts
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    MongooseModule.forRootAsync({                     // ulanish (8.3: 2.4)
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        uri: config.get("MONGO_URI"),                 // .env (14, 8.14)
      }),
    }),
  ],
})
export class AppModule {}

MongooseModule.forRootAsync — MongoDB ulanish (URI env'dan — 14, 8.3: 2.4). Bir marta (root), butun ilovaga. forRoot (oddiy) yoki forRootAsync (ConfigService — afzal). Bu — 6.2'dagi mongoose.connectning NestJS versiyasi (DI bilan). Connection pool avtomatik 6.15-bob.

2.2. Schema dekoratorlar (@Schema, @Prop)

ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";

export type UserDocument = HydratedDocument<User>;

@Schema({ timestamps: true })                         // createdAt/updatedAt avtomatik (6.2)
export class User {
  @Prop({ required: true })                           // majburiy (6.2)
  ism: string;

  @Prop({ required: true, unique: true, lowercase: true })   // noyob, kichik harf
  email: string;

  @Prop({ required: true, select: false })           // so'rovda chiqmaydi (parol — 14)
  parol: string;

  @Prop({ default: "user", enum: ["user", "admin"] })   // default + enum
  rol: string;

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

export const UserSchema = SchemaFactory.createForClass(User);   // schema yaratish

Schema dekoratorlar: @Schema() (klass = schema; timestamps — 6.2), @Prop() (maydon — required/unique/default/enum — 6.2). SchemaFactory.createForClass — TS klassdan Mongoose schema. TypeScript klass = schema (tur xavfsiz — 7, kamroq boilerplate). select: false (parol yashirish — 14). Bu — 6.2 schema'sining toza, dekorator versiyasi.

@Prop() — validatsiya opsiyalarining to'liq to'plamini oladi 6.2-bob. Ular DB'ga yozishdan oldin ishlaydi (schema darajasidagi validatsiya — DTO validatsiyasidan 8.5-bob tashqari, ikkinchi himoya qavati):

ts
@Schema({ timestamps: true })
export class Product {
  @Prop({ required: true, trim: true, minlength: 3, maxlength: 120 })   // uzunlik chegarasi
  nom: string;

  @Prop({ required: true, min: 0 })                   // manfiy bo'lmasin (son)
  narx: number;

  @Prop({ min: 0, default: 0 })
  zaxira: number;

  @Prop({ match: /^[a-z0-9-]+$/, unique: true })      // regex tekshiruv (slug formati)
  slug: string;

  @Prop({ enum: ["yangi", "eski", "buzuq"], default: "yangi" })   // faqat ruxsat etilgan qiymat
  holat: string;

  @Prop({ type: Date, default: Date.now, immutable: true })   // yaratilgach o'zgarmaydi
  yaratilganVaqt: Date;

  @Prop({ type: [String], default: [] })              // primitiv massiv (teglar)
  teglar: string[];

  @Prop({ type: Map, of: String })                    // Map turi (kalit-qiymat)
  xususiyatlar: Map<string, string>;
}

@Prop validatsiya opsiyalari 6.2-bob: minlength/maxlength (satr uzunligi), min/max (son chegarasi), match (regex), enum (ruxsat etilgan qiymatlar), trim/lowercase/uppercase (satr normalizatsiya), immutable (yaratilgach o'zgarmaydi), validate (maxsus funksiya). Bular — DB darajasida ishonchli himoya (DTO — 8.5 — kirish darajasi; schema — saqlash darajasi). type: [String] (primitiv massiv), type: Map (dinamik kalit-qiymat).

2.3. Model ro'yxati (forFeature) va @InjectModel

ts
// users.module.ts
@Module({
  imports: [
    MongooseModule.forFeature([                       // model ro'yxati (8.3: forFeature kabi)
      { name: User.name, schema: UserSchema },
    ]),
  ],
  providers: [UsersService],
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,   // DI (6.2 model)
  ) {}
}

forFeature — modul'da model ro'yxati (TypeORM forFeature kabi — 8.3: 2.6). @InjectModel(User.name) — DI bilan Mongoose model (6.2 — mongoose.modelning DI versiyasi). Model<UserDocument> — tur xavfsiz. Service'da model bilan ishlanadi (CRUD — 2.4).

2.4. CRUD (Mongoose model — 6.2)

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

  async yarat(dto: CreateUserDto): Promise<User> {
    return new this.userModel(dto).save();           // create (6.2: 2.5)
  }
  async hammasi(): Promise<User[]> {
    return this.userModel.find().exec();             // barchasi (6.2)
  }
  async bitta(id: string): Promise<User> {
    const user = await this.userModel.findById(id).exec();
    if (!user) throw new NotFoundException("Topilmadi");   // (8.1)
    return user;
  }
  async yangila(id: string, dto: UpdateUserDto): Promise<User> {
    return this.userModel.findByIdAndUpdate(id, dto, { new: true }).exec();   // new: yangilangani
  }
  async ochir(id: string): Promise<void> {
    await this.userModel.findByIdAndDelete(id).exec();
  }
}

CRUD (6.2: 2.5): new model(dto).save() / create (yaratish), find/findById (o'qish), findByIdAndUpdate (new: true — yangilangan hujjat), findByIdAndDelete. .exec() (Promise — yaxshi amaliyot). NotFound 8.1-bob. Bu — 6.2 CRUD'ning service'dagi joylashuvi (controller yupqa — 8.1: 2.7).

2.5. Query (filter, saralash, sahifalash — 6.2)

ts
async qidir(filter: FilterDto) {
  const query = this.userModel.find();
  if (filter.rol) query.where("rol").equals(filter.rol);   // filter (6.2)
  if (filter.qidiruv) query.where("ism", new RegExp(filter.qidiruv, "i"));   // regex
  return query
    .sort({ createdAt: -1 })                          // saralash (yangi birinchi)
    .skip((filter.sahifa - 1) * filter.limit)         // sahifalash (6.2)
    .limit(filter.limit)
    .select("-parol")                                  // parolsiz (14)
    .exec();
}

Query 6.2-bob: find().where().equals() (filter), RegExp (qidiruv), sort (saralash), skip/limit (sahifalash — keyset afzal katta datada — 6.10), select("-parol") (maydon tanlash — 14). Query chaining (zanjir). Bu — 6.2 query'sining NestJS service'dagi amaliyoti.

2.6. Relations: ref va populate (6.3)

ts
import { Types } from "mongoose";

@Schema({ timestamps: true })
export class Order {
  @Prop({ type: Types.ObjectId, ref: "User", required: true })   // ref (6.3)
  user: Types.ObjectId;

  @Prop([{ type: Types.ObjectId, ref: "Product" }])   // massiv ref
  mahsulotlar: Types.ObjectId[];

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

// populate — bog'langan hujjatni olib kelish (JOIN muqobili — 6.3)
async buyurtma(id: string) {
  return this.orderModel.findById(id)
    .populate("user", "ism email")                    // user'ni to'ldirish (faqat ism/email)
    .populate("mahsulotlar")
    .exec();
}

ref + populate 6.3-bob: @Prop({ type: Types.ObjectId, ref: "User" }) — boshqa hujjatga havola. populate("user") — havolani to'ldirish (ObjectId to'liq hujjat — SQL JOIN muqobili — 6.3). Ikkinchi argument — maydon tanlash ("ism email"). Massiv ref ham. Bu — NoSQL'da bog'lanishning asosiy usuli (reference — 6.3).

2.7. Embed vs Reference (6.3 — muhim qaror)

text
  EMBED (ichiga joylash) — birga o'qiladigan, kichik:
  @Prop({ type: [AddressSchema] }) manzillar: Address[];   // hujjat ichida
   tez o'qish (bir so'rov), lekin takror/o'sish muammosi

  REFERENCE (havola) — katta, mustaqil, ko'p bog'lanish:
  @Prop({ type: Types.ObjectId, ref: "Product" })  populate
   moslashuvchan, lekin populate (qo'shimcha so'rov)

  Qoida 6.3-bob: "birga so'raladi + kichik"  embed; "katta/mustaqil/N:M"  reference

Embed vs Reference (6.3 — NoSQL'ning asosiy dizayn qarori): embed (hujjat ichiga — manzil, kichik ro'yxat — bir so'rovda, tez); reference (havola + populate — katta/mustaqil/ko'p bog'lanish). Qoida: "birga o'qiladi + kichik + cheklangan" embed; aks holda reference. Bu — sxema dizaynining yuragi (SQL normalizatsiyasidan farq — 6.9).

2.8. Virtuallar (hisoblanadigan maydon — 6.3)

ts
@Schema({
  timestamps: true,
  toJSON: { virtuals: true },                         // JSON'da virtual ko'rsatish
})
export class User {
  @Prop() ism: string;
  @Prop() familiya: string;
}
const UserSchema = SchemaFactory.createForClass(User);

// Virtual — DB'da saqlanmaydi, dinamik hisoblanadi
UserSchema.virtual("toliqIsm").get(function () {
  return `${this.ism} ${this.familiya}`;             // ism + familiya
});

// Virtual populate (bog'langan hujjatlarni virtual sifatida)
UserSchema.virtual("buyurtmalar", {
  ref: "Order", localField: "_id", foreignField: "user",
});

Virtual 6.3-bob — DB'da saqlanmaydigan, dinamik hisoblanadigan maydon (toliqIsm = ism+familiya). toJSON: { virtuals: true } (JSON'da chiqishi uchun). Virtual populate — bog'langan hujjatlarni virtual sifatida (foreign key teskari — user'ning buyurtmalari). Hisoblanadigan qiymat, teskari bog'lanish uchun.

2.9. Aggregation pipeline (6.3 — kuchli)

ts
// Statistika: har foydalanuvchi nechta buyurtma, jami summa (6.3 aggregation)
async statistika() {
  return this.orderModel.aggregate([
    { $match: { holat: "tugallangan" } },             // filtr (6.3)
    { $group: {                                        // guruhlash
      _id: "$user",
      jamiBuyurtma: { $sum: 1 },
      jamiSumma: { $sum: "$summa" },
    }},
    { $sort: { jamiSumma: -1 } },                      // saralash
    { $limit: 10 },                                    // top 10
    { $lookup: {                                       // JOIN (6.3)
      from: "users", localField: "_id", foreignField: "_id", as: "user",
    }},
  ]);
}

Aggregation pipeline (6.3 — MongoDB'ning kuchli tahlil vositasi): bosqichlar zanjiri — $match (filtr), $group (guruhlash + $sum/$avg), $sort, $limit, $lookup (JOIN). Murakkab hisobot, statistika uchun (oddiy query yetmaganda). Bu — 6.3 aggregation'ining NestJS amaliyoti (SQL GROUP BY muqobili).

2.10. Hook'lar (middleware — pre/post — 6.2)

ts
// Parolni saqlashdan oldin hash (6.2 hook, 5.15)
UserSchema.pre("save", async function (next) {
  if (this.isModified("parol")) {                     // faqat o'zgargan bo'lsa
    this.parol = await bcrypt.hash(this.parol, 12);   // (5.15)
  }
  next();
});

// post("save") — saqlangandan keyin (audit, xabar, log — 6.2)
UserSchema.post("save", function (doc, next) {
  console.log(`Yangi foydalanuvchi saqlandi: ${doc.email}`);   // masalan: audit log
  next();
});

// pre query hook — har "find"da o'chirilganlarni yashirish (soft delete — 6.2)
UserSchema.pre(/^find/, function (next) {                       // find/findOne/findById...
  this.where({ ochirilgan: { $ne: true } });                   // faqat o'chirilmaganlar
  next();
});

// Metod qo'shish (instance method)
UserSchema.methods.parolTekshir = async function (parol: string) {
  return bcrypt.compare(parol, this.parol);          // (5.15)
};

// Statik metod (model darajasida — 6.2)
UserSchema.statics.emailBilanTop = function (email: string) {
  return this.findOne({ email });
};

Hook'lar (Mongoose middleware — 6.2): pre("save") (saqlashdan oldin — parol hash — 5.15), post("save") (saqlangandan keyin — doc argument bilan — audit/log/xabar). isModified (faqat o'zgarganda). Query hook (pre(/^find/)) — har o'qish so'roviga qoida qo'shadi (masalan soft delete filtri). methods (instance metod — hujjatda), statics (model metod — modelda). Avtomatik mantiq (audit, hash, validatsiya). Schema fayl'da (dekoratordan keyin). Bu — 6.2 hook'lari.

2.11. Transaction (atomik — 6.5 ruhida)

ts
async pulOtkaz(fromId: string, toId: string, summa: number) {
  const session = await this.connection.startSession();   // @InjectConnection
  session.startTransaction();
  try {
    await this.accountModel.updateOne({ _id: fromId }, { $inc: { balans: -summa } }, { session });
    await this.accountModel.updateOne({ _id: toId }, { $inc: { balans: summa } }, { session });
    await session.commitTransaction();                // tasdiqlash (hammasi)
  } catch (e) {
    await session.abortTransaction();                 // bekor (hech narsa)
    throw e;
  } finally {
    session.endSession();
  }
}

Transaction (atomik — 6.9 ACID): startSession startTransaction amallar ({ session }) commit (hammasi) yoki abort (hech narsa). Pul o'tkazish, buyurtma (bir nechta hujjat birga o'zgarishi) uchun. MongoDB transaction — replica set kerak (single node'da yo'q). @InjectConnection (connection olish).

2.12. Indekslar (tezlik — 6.3, 6.10)

ts
@Schema()
export class Product {
  @Prop({ index: true })                             // oddiy indeks
  kategoriya: string;

  @Prop({ unique: true })                            // noyob indeks
  slug: string;
}
const ProductSchema = SchemaFactory.createForClass(Product);

// Composite/text indeks
ProductSchema.index({ kategoriya: 1, narx: -1 });    // composite (ESR — 6.3)
ProductSchema.index({ nom: "text", tavsif: "text" });   // text search (qidiruv)

TTL indeks — hujjat ma'lum vaqtdan keyin avtomatik o'chadi (sessiya, OTP kod, vaqtinchalik token, kesh uchun — 6.3):

ts
@Schema()
export class Sessiya {
  @Prop({ required: true })
  token: string;

  @Prop({ type: Date, default: Date.now })
  yaratilgan: Date;
}
const SessiyaSchema = SchemaFactory.createForClass(Sessiya);

// TTL: yaratilgandan 3600 soniya (1 soat) keyin hujjat o'chadi
SessiyaSchema.index({ yaratilgan: 1 }, { expireAfterSeconds: 3600 });

// Yoki aniq muddat: expiresAt maydoni kelganda o'chadi
// SessiyaSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });

Indekslar (6.3, 6.10): @Prop({ index: true }) (oddiy), unique (noyob), schema.index({...}) (composite — ESR qoidasi — 6.3), text (matn qidiruv), TTL (expireAfterSeconds — Date maydon bo'yicha; hujjat muddati o'tgach MongoDB avtomatik o'chiradi — sessiya/OTP/kesh uchun ideal). Indeks — tez o'qish (6.10 — B-tree). Production'da autoIndex: false (indeks qo'lda yoki migratsiyada yarating — 6.10). Tez-tez so'raladigan maydonga indeks 6.10-bob. TTL o'chirish darhol emas — MongoDB ~60 soniyada bir tekshiradi.

2.13. Lean query (tezlik — faqat o'qish)

ts
// lean — to'liq Mongoose hujjat emas, oddiy JS obyekt (tez, yengil)
async royxat() {
  return this.userModel.find().lean().exec();        // tezroq (metod/virtual yo'q)
}

lean() — Mongoose find odatda to'liq hujjat (metodlar, virtuallar, kuzatuv — og'ir). lean() — oddiy JS obyekt qaytaradi (tez, kam xotira). Faqat o'qish uchun (o'zgartirmaganda — ro'yxat, API javob). Katta ro'yxatda sezilarli tezlik. Best practice (faqat o'qishda).

2.14. Discriminator (bitta to'plamda ko'p tur — meros)

Discriminator — bitta collectionda bir nechta o'xshash, lekin farqli schema'ni saqlash imkoni (obyektga yo'naltirilgan merosning MongoDB'dagi ko'rinishi — 7). Asos schema bor; undan "vorislar" o'z qo'shimcha maydonlari bilan ajraladi. Mongoose har hujjatga __t maydonini qo'shib turini belgilaydi.

ts
// Asos schema — umumiy maydonlar
@Schema({ discriminatorKey: "kind", timestamps: true })   // kind — tur ajratkichi
export class Hodisa {
  @Prop({ required: true })
  sarlavha: string;

  @Prop()
  kind: string;                                     // discriminator kaliti
}
export const HodisaSchema = SchemaFactory.createForClass(Hodisa);

// Voris 1 — Klik hodisasi (qo'shimcha maydon)
@Schema()
export class Klik extends Hodisa {
  @Prop({ required: true })
  tugma: string;                                    // qaysi tugma bosildi
}
export const KlikSchema = SchemaFactory.createForClass(Klik);

// Voris 2 — Xarid hodisasi
@Schema()
export class Xarid extends Hodisa {
  @Prop({ required: true })
  summa: number;
}
export const XaridSchema = SchemaFactory.createForClass(Xarid);

Modulda discriminator sifatida ro'yxatga olinadi:

ts
@Module({
  imports: [
    MongooseModule.forFeature([{
      name: Hodisa.name,
      schema: HodisaSchema,
      discriminators: [                             // vorislar — bir collection'da
        { name: Klik.name, schema: KlikSchema },
        { name: Xarid.name, schema: XaridSchema },
      ],
    }]),
  ],
})
export class HodisalarModule {}

// Service — asos model orqali ham, voris model orqali ham ishlash mumkin
@Injectable()
export class HodisalarService {
  constructor(
    @InjectModel(Hodisa.name) private hodisaModel: Model<Hodisa>,
    @InjectModel(Klik.name) private klikModel: Model<Klik>,   // voris ham inject qilinadi
  ) {}

  async klikYoz(sarlavha: string, tugma: string) {
    return this.klikModel.create({ sarlavha, tugma });   // kind: "Klik" avtomatik qo'yiladi
  }

  async hammaHodisa() {
    return this.hodisaModel.find();                // barcha turlar (klik+xarid) birga keladi
  }
}

Discriminator (meros — 7): discriminatorKey bilan bitta collection'da farqli, lekin qarindosh turlarni saqlaydi (hodisa jurnali, to'lov usullari, bildirishnoma turlari uchun ideal). Asos schema — umumiy maydonlar; voris schema (extends) — qo'shimcha maydonlar. forFeatureda discriminators ro'yxati. Mongoose __t (yoki discriminatorKey) bilan turni saqlaydi. Asos model — barcha turlar; voris model — faqat o'sha tur. SQL'dagi "single table inheritance" 8.4-bob muqobili.

2.15. SQL vs MongoDB NestJS'da (taqqoslash)

text
  SQL (TypeORM/Prisma — 8.3)        MongoDB (Mongoose — 8.13)
  @Entity / model                   @Schema
  @Column                           @Prop
  @InjectRepository                 @InjectModel
  Repository<T>                     Model<T>
  relations / include               populate
  QueryBuilder                      aggregate
  migration (majburiy)              schema moslashuvchan (migratsiya kam)
  qat'iy tur/relation               moslashuvchan hujjat

NestJS'da SQL 8.3-bob va MongoDB 8.13-bob o'xshash naqsh (DI, modul, service) — lekin: SQL qat'iy (schema/migration — 6.14), MongoDB moslashuvchan (hujjat). Tanlov 6.1-bob: murakkab relation/tranzaksiya SQL; moslashuvchan/tez/ichki hujjat MongoDB. NestJS ikkalasini bir xil uslubda qo'llaydi.

2.16. Best practices (Mongoose NestJS)

text
   Schema dekorator (@Schema/@Prop — tur xavfsiz — 2.2)
   @InjectModel (DI — 2.3); service'da (controller yupqa — 8.1: 2.7)
   select: false (parol — 14, 2.2); .lean() (o'qishda — 2.13)
   Embed vs Reference to'g'ri (6.3 — 2.7)
   Indeks (tez-tez so'raladigan — 6.10, 2.12); autoIndex: false (prod)
   Hook (hash/audit — 2.10); virtual (hisoblanadigan — 2.8)
   Transaction (atomik — replica set — 2.11)
   TTL indeks (sessiya/OTP/kesh — avtomatik o'chirish — 2.12)
   Discriminator (bir collection — ko'p tur — 2.14)
   URI .env (14, 2.1); aggregation (murakkab tahlil — 2.9)

3. Sintaksis — tez ma'lumotnoma

ts
// Ulanish 2.1-bob: MongooseModule.forRootAsync({ useFactory: () => ({ uri }) })

// Schema (2.2)
@Schema({ timestamps: true })
class User { @Prop({ required: true }) ism: string; }
const UserSchema = SchemaFactory.createForClass(User);

// Model 2.3-bob: forFeature([{ name: User.name, schema: UserSchema }])
@InjectModel(User.name) private model: Model<UserDocument>

// CRUD 2.4-bob: model.create/find/findById/findByIdAndUpdate/findByIdAndDelete
// Populate 2.6-bob: .populate("user", "ism email")
// Aggregate 2.9-bob: model.aggregate([{ $match }, { $group }, { $lookup }])

4. Batafsil kod namunalari

Misol 1 — To'liq schema (User — 2.2, 2.10)

ts
// schemas/user.schema.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";
import * as bcrypt from "bcrypt";

export type UserDocument = HydratedDocument<User>;

@Schema({ timestamps: true, toJSON: { virtuals: true } })
export class User {
  @Prop({ required: true, trim: true })
  ism: string;

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

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

  @Prop({ required: true, select: false })           // parol yashirish (14)
  parol: string;

  @Prop({ default: "user", enum: ["user", "admin", "moderator"] })
  rol: string;

  @Prop({ default: false })
  emailTasdiqlangan: boolean;

  @Prop()
  avatar?: string;
}

export const UserSchema = SchemaFactory.createForClass(User);

// Virtual (hisoblanadigan — 2.8)
UserSchema.virtual("toliqIsm").get(function () {
  return `${this.ism} ${this.familiya}`;
});

// Hook (parol hash — 2.10, 5.15)
UserSchema.pre("save", async function (next) {
  if (this.isModified("parol")) this.parol = await bcrypt.hash(this.parol, 12);
  next();
});

// Index (2.12)
UserSchema.index({ email: 1 });
UserSchema.index({ rol: 1, createdAt: -1 });

Misol 2 — Module va Service (2.3, 2.4)

ts
// users.module.ts
@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  async yarat(dto: CreateUserDto): Promise<UserDocument> {
    const mavjud = await this.userModel.findOne({ email: dto.email });
    if (mavjud) throw new ConflictException("Email band");   // (8.1)
    return new this.userModel(dto).save();           // hook  parol hash (2.10)
  }

  async hammasi(filter: FilterDto) {
    const q = this.userModel.find();
    if (filter.rol) q.where("rol").equals(filter.rol);
    return q.sort({ createdAt: -1 })
      .skip((filter.sahifa - 1) * filter.limit)
      .limit(filter.limit)
      .lean()                                          // tez o'qish (2.13)
      .exec();
  }

  async bitta(id: string): Promise<UserDocument> {
    const user = await this.userModel.findById(id).exec();
    if (!user) throw new NotFoundException("Foydalanuvchi topilmadi");
    return user;
  }

  async emailBilan(email: string): Promise<UserDocument> {
    return this.userModel.findOne({ email }).select("+parol").exec();   // parol bilan (auth — 8.9)
  }

  async yangila(id: string, dto: UpdateUserDto): Promise<UserDocument> {
    const user = await this.userModel.findByIdAndUpdate(id, dto, { new: true }).exec();
    if (!user) throw new NotFoundException("Topilmadi");
    return user;
  }

  async ochir(id: string): Promise<void> {
    const natija = await this.userModel.findByIdAndDelete(id).exec();
    if (!natija) throw new NotFoundException("Topilmadi");
  }
}

Misol 3 — Ref va populate (Order — 2.6)

ts
// schemas/order.schema.ts
@Schema({ timestamps: true })
export class Order {
  @Prop({ type: Types.ObjectId, ref: "User", required: true })
  user: Types.ObjectId;

  @Prop([{
    mahsulot: { type: Types.ObjectId, ref: "Product" },
    miqdor: Number,
    narx: Number,
  }])
  buyumlar: { mahsulot: Types.ObjectId; miqdor: number; narx: number }[];

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

  @Prop({ default: "yangi", enum: ["yangi", "tayyorlanmoqda", "yetkazildi", "bekor"] })
  holat: string;
}
export const OrderSchema = SchemaFactory.createForClass(Order);

// Service — populate (2.6)
@Injectable()
export class OrdersService {
  constructor(@InjectModel(Order.name) private orderModel: Model<OrderDocument>) {}

  async bitta(id: string) {
    return this.orderModel.findById(id)
      .populate("user", "ism email")                  // user to'ldirish (faqat ism/email)
      .populate("buyumlar.mahsulot", "nom rasm")      // ichki ref to'ldirish
      .exec();
  }

  async userBuyurtmalari(userId: string) {
    return this.orderModel.find({ user: userId })
      .sort({ createdAt: -1 })
      .populate("buyumlar.mahsulot")
      .lean()
      .exec();
  }
}

Misol 4 — Aggregation: statistika (2.9)

ts
// Eng faol mijozlar (top 10)
async topMijozlar() {
  return this.orderModel.aggregate([
    { $match: { holat: "yetkazildi" } },
    { $group: {
      _id: "$user",
      buyurtmaSoni: { $sum: 1 },
      jamiXarid: { $sum: "$jamiSumma" },
      ortacha: { $avg: "$jamiSumma" },
    }},
    { $sort: { jamiXarid: -1 } },
    { $limit: 10 },
    { $lookup: { from: "users", localField: "_id", foreignField: "_id", as: "mijoz" } },
    { $unwind: "$mijoz" },                            // massivni ochish
    { $project: {                                      // tanlash/shakllantirish
      ism: "$mijoz.ism", email: "$mijoz.email",
      buyurtmaSoni: 1, jamiXarid: 1, ortacha: { $round: ["$ortacha", 0] },
    }},
  ]);
}

// Oylik savdo hisoboti
async oylikSavdo(yil: number) {
  return this.orderModel.aggregate([
    { $match: { createdAt: { $gte: new Date(`${yil}-01-01`) }, holat: "yetkazildi" } },
    { $group: { _id: { $month: "$createdAt" }, savdo: { $sum: "$jamiSumma" } } },
    { $sort: { _id: 1 } },
  ]);
}

Misol 5 — Transaction (buyurtma + zaxira — 2.11)

ts
import { InjectConnection } from "@nestjs/mongoose";
import { Connection } from "mongoose";

@Injectable()
export class OrdersService {
  constructor(
    @InjectModel(Order.name) private orderModel: Model<OrderDocument>,
    @InjectModel(Product.name) private productModel: Model<ProductDocument>,
    @InjectConnection() private connection: Connection,
  ) {}

  async buyurtmaYarat(userId: string, buyumlar: any[]) {
    const session = await this.connection.startSession();
    session.startTransaction();
    try {
      let jami = 0;
      for (const b of buyumlar) {
        const mahsulot = await this.productModel.findById(b.mahsulot).session(session);
        if (!mahsulot || mahsulot.zaxira < b.miqdor) {
          throw new BadRequestException(`${mahsulot?.nom}: zaxira yetarli emas`);
        }
        mahsulot.zaxira -= b.miqdor;                  // zaxira kamaytirish
        await mahsulot.save({ session });
        jami += mahsulot.narx * b.miqdor;
      }
      const [order] = await this.orderModel.create([{ user: userId, buyumlar, jamiSumma: jami }], { session });
      await session.commitTransaction();             // hammasi muvaffaqiyatli
      return order;
    } catch (e) {
      await session.abortTransaction();              // xato  bekor (zaxira qaytadi)
      throw e;
    } finally {
      session.endSession();
    }
  }
}

Misol 6 — Text search va qidiruv (2.12)

ts
// Schema — text index
ProductSchema.index({ nom: "text", tavsif: "text" });

// Service — qidiruv
async qidir(matn: string, sahifa = 1) {
  return this.productModel.find(
    { $text: { $search: matn } },                    // text qidiruv
    { score: { $meta: "textScore" } },               // moslik bahosi
  )
    .sort({ score: { $meta: "textScore" } })         // moslik bo'yicha
    .skip((sahifa - 1) * 20).limit(20)
    .lean().exec();
}

// Regex qidiruv (kichik datada — indekssiz)
async tezQidir(matn: string) {
  return this.productModel.find({ nom: new RegExp(matn, "i") }).limit(10).lean();
}

Misol 7 — Controller (ObjectId pipe — 8.5)

ts
import { isValidObjectId } from "mongoose";

// Custom pipe — ObjectId tekshirish (8.5)
@Injectable()
export class ParseObjectIdPipe implements PipeTransform {
  transform(value: string) {
    if (!isValidObjectId(value)) throw new BadRequestException("Noto'g'ri ID");
    return value;
  }
}

@Controller("users")
export class UsersController {
  constructor(private usersService: UsersService) {}

  @Get(":id")
  bitta(@Param("id", ParseObjectIdPipe) id: string) {   // ID validatsiya (8.5)
    return this.usersService.bitta(id);
  }

  @Post()
  yarat(@Body() dto: CreateUserDto) {                 // DTO validatsiya (8.5)
    return this.usersService.yarat(dto);
  }
}

Misol 8 — Nested schema (embed — 2.7)

ts
// Embedded sub-schema (manzil — hujjat ichida)
@Schema({ _id: false })                              // ichki — alohida _id kerak emas
export class Address {
  @Prop({ required: true }) shahar: string;
  @Prop({ required: true }) kocha: string;
  @Prop() uy?: string;
}
const AddressSchema = SchemaFactory.createForClass(Address);

@Schema({ timestamps: true })
export class User {
  @Prop({ required: true }) ism: string;

  @Prop({ type: [AddressSchema], default: [] })      // embed massiv (2.7)
  manzillar: Address[];

  @Prop({ type: AddressSchema })                      // embed bitta
  asosiyManzil?: Address;
}
//  manzil user bilan birga o'qiladi (bir so'rov — embed — 2.7)

Misol 9 — Virtual populate (teskari bog'lanish — 2.8)

ts
@Schema({ toJSON: { virtuals: true }, toObject: { virtuals: true } })
export class User {
  @Prop() ism: string;
}
const UserSchema = SchemaFactory.createForClass(User);

// User'ning buyurtmalari (teskari — Order.user  User)
UserSchema.virtual("buyurtmalar", {
  ref: "Order",
  localField: "_id",
  foreignField: "user",                              // Order'dagi user maydoni
});

// Ishlatish
async userToliq(id: string) {
  return this.userModel.findById(id)
    .populate("buyurtmalar")                         // virtual populate
    .exec();
}

Misol 10 — To'liq modul tuzilishi (2.15)

text
  src/
  ├── app.module.ts          (MongooseModule.forRootAsync — 2.1)
  ├── users/
  │   ├── schemas/user.schema.ts    (@Schema/@Prop — Misol 1)
  │   ├── dto/                       8.5-bob
  │   ├── users.service.ts          (@InjectModel — Misol 2)
  │   ├── users.controller.ts       (Misol 7)
  │   └── users.module.ts           (forFeature — Misol 2)
  ├── orders/
  │   ├── schemas/order.schema.ts   (ref — Misol 3)
  │   └── ...
  └── products/

   Har modul: schema + dto + service + controller + module (8.1 tuzilma)
   MongoDB 8.13-bob yoki SQL 8.3-bob — bir xil NestJS naqsh (2.15)

5. To'g'ri va noto'g'ri holatlar

1) Parol select: false'siz

text
 parol so'rovda chiqadi (14)
 @Prop({ select: false }) + .select("+parol") (auth'da)

2) Hammasini embed yoki hammasini reference

text
 ko'r-ko'rona (o'sish/populate muammosi — 2.7)
 qoida: kichik+birga  embed; katta/mustaqil  reference (6.3)

3) O'qishda to'liq hujjat (lean'siz)

text
 katta ro'yxat to'liq hujjat (sekin — 2.13)
 .lean() (faqat o'qishda)

4) Indekssiz tez-tez so'rov

text
 filter maydon indekssiz (sekin — 6.10)
 @Prop({ index: true }) / schema.index

5) Ko'p hujjat o'zgarishi transaction'siz

text
 buyurtma + zaxira alohida (yarmida xato — nomutanosib)
 transaction (atomik — 2.11)

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Schema hasn't been registered for model

Sababi: forFeature'da model yo'q 2.3-bob. Yechimi: MongooseModule.forFeature([{ name, schema }]) modulda.

Xato 2 — populate bo'sh (null)

Sababi: ref nomi noto'g'ri yoki ObjectId mos emas 2.6-bob. Yechimi: ref model nomi aniq; type: ObjectId.

Xato 3 — Virtual JSON'da ko'rinmaydi

Sababi: toJSON virtuals yo'q 2.8-bob. Yechimi: @Schema({ toJSON: { virtuals: true } }).

Xato 4 — Transaction xato (Transaction numbers...)

Sababi: standalone MongoDB (replica set yo'q — 2.11). Yechimi: replica set; yoki Atlas/Docker replica.

Xato 5 — Sekin so'rov

Sababi: indeks yo'q yoki lean'siz (2.12, 2.13). Yechimi: indeks; lean; explain() bilan tekshir 6.10-bob.

Xato 6 — Cast to ObjectId failed

Sababi: noto'g'ri ID format. Yechimi: ParseObjectIdPipe (Misol 7).


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Mongoose (6.2, 6.3): schema, populate, aggregation — NestJS'da.
  • DB ulanish 8.3-bob: MongooseModule.
  • DTO/Pipe 8.5-bob: validatsiya, ObjectId pipe.
  • Auth 8.9-bob: user schema, emailBilan.
  • Bot 8.12-bob: MongoDB ko'p ishlatiladi.
  • Config 8.14-bob: MONGO_URI.
  • NoSQL 6.1-bob: embed vs reference.
  • Indeks 6.10-bob: tezlik.
  • SQL 8.3-bob: taqqoslash 2.15-bob.

8. Eng yaxshi amaliyotlar (best practices)

  • Schema dekorator (@Schema/@Prop — tur xavfsiz — 2.2).
  • @InjectModel (DI — 2.3); service'da (controller yupqa — 8.1: 2.7).
  • select: false (parol — 14); .lean() (o'qishda — 2.13).
  • Embed vs Reference to'g'ri (kichik+birga vs katta/mustaqil — 6.3, 2.7).
  • Indeks (tez-tez so'raladigan — 6.10); autoIndex: false (prod).
  • Hook (hash/audit — 2.10); virtual (hisoblanadigan — 2.8).
  • Transaction (atomik — replica set — 2.11).
  • URI .env (14); aggregation (murakkab tahlil — 2.9).
  • ParseObjectIdPipe (ID validatsiya — 8.5).
  • DTO validatsiya 8.5-bob.

9. Amaliy loyiha: "MongoDB E-commerce Backend (NestJS)"

NestJS + MongoDB'ni mustahkamlash.

Maqsad

NestJS + Mongoose bilan e-commerce backend: user, mahsulot, buyurtma — populate, aggregation, transaction bilan.

Talablar (requirements)

  1. Ulanish: MongooseModule.forRootAsync (env — Misol 2, 2.1).
  2. Schema'lar: User (hook+virtual), Product, Order (ref) — Misol 1, 3, 2.2.
  3. CRUD: user/product/order service (Misol 2, 2.4).
  4. Populate: buyurtma user + mahsulot (Misol 3, 2.6).
  5. Embed: user manzillari (Misol 8, 2.7).
  6. Virtual: toliqIsm, virtual populate buyurtmalar (Misol 1, 9, 2.8).
  7. Aggregation: top mijozlar, oylik savdo (Misol 4, 2.9).
  8. Transaction: buyurtma + zaxira (Misol 5, 2.11).
  9. Indeks + text search: mahsulot qidiruv (Misol 6, 2.12).
  10. Pipe + DTO: ObjectId pipe, validatsiya (Misol 7, 8.5).

Maslahatlar (hint)

  • forFeature har modulda (2.3, 1-xato).
  • select: false parol (14, 1-holat).
  • Embed vs reference qoidasi (6.3, 2.7).
  • lean o'qishda (2.13, 3-holat).
  • Transaction replica set (2.11, 4-xato).
  • Indeks tez-tez so'raladiganga 6.10-bob.

"Tayyor" mezonlari (acceptance criteria)

  • Ulanish (forRootAsync).
  • Schema'lar (hook, virtual, index).
  • CRUD (3 model).
  • Populate (ref).
  • Embed (manzil).
  • Virtual (toliqIsm, populate).
  • Aggregation (statistika).
  • Transaction (atomik).
  • Text search.
  • Pipe + DTO.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS + MongoDB'ni chuqur o'rgandik:

  • MongooseModule (forRootAsync — 2.1); schema dekorator (@Schema/@Prop — tur xavfsiz — 2.2); @InjectModel (DI — 2.3); CRUD 2.4-bob; query 2.5-bob.
  • Ref + populate (JOIN muqobili — 2.6); embed vs reference (dizayn qarori — 2.7); virtual (hisoblanadigan + teskari populate — 2.8).
  • Aggregation (statistika — 2.9); hook (hash/audit — 2.10); transaction (atomik — 2.11).
  • Indeks (tezlik — 2.12); lean (o'qish — 2.13); discriminator 2.14-bob; SQL vs MongoDB 2.15-bob.

Keyingi bob — 8.14-bob: Config moduli — env, validatsiya, namespaced config. MongoDB'ni bildik; endi har modulda ishlatgan ConfigServiceni chuqur ko'ramiz: @nestjs/config, env validatsiya (Joi), namespaced config (registerAs), turli muhit (dev/prod). Bu — barcha sozlamalarni (DB, JWT, SMTP — 5.8) markazlashtiradi.


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com/techniques/mongodb (@nestjs/mongoose, @Schema, @Prop, SchemaFactory)
  • mongoosejs.com — populate, aggregate, transactions, virtuals, discriminators, subdocuments

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.13-bob: NestJS + MongoDB (Mongoose chuqur) — Wisar