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)
// 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) yokiforRootAsync(ConfigService — afzal). Bu — 6.2'dagimongoose.connectning NestJS versiyasi (DI bilan). Connection pool avtomatik 6.15-bob.
2.2. Schema dekoratorlar (@Schema, @Prop)
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 yaratishSchema 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):
@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
// 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)
@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)
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)
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)
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" referenceEmbed 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)
@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)
// 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)
// 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 —docargument 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)
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):
startSessionstartTransactionamallar ({ session })commit(hammasi) yokiabort(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)
@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):
@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'daautoIndex: 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)
// 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
findodatda 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.
// 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:
@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):
discriminatorKeybilan 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.forFeaturedadiscriminatorsro'yxati. Mongoose__t(yokidiscriminatorKey) 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)
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 hujjatNestJS'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)
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
// 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)
// 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)
// 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)
// 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)
// 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)
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)
// 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)
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)
// 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)
@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)
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
parol so'rovda chiqadi (14)
@Prop({ select: false }) + .select("+parol") (auth'da)2) Hammasini embed yoki hammasini reference
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)
katta ro'yxat to'liq hujjat (sekin — 2.13)
.lean() (faqat o'qishda)4) Indekssiz tez-tez so'rov
filter maydon indekssiz (sekin — 6.10)
@Prop({ index: true }) / schema.index5) Ko'p hujjat o'zgarishi transaction'siz
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)
- Ulanish: MongooseModule.forRootAsync (env — Misol 2, 2.1).
- Schema'lar: User (hook+virtual), Product, Order (ref) — Misol 1, 3, 2.2.
- CRUD: user/product/order service (Misol 2, 2.4).
- Populate: buyurtma user + mahsulot (Misol 3, 2.6).
- Embed: user manzillari (Misol 8, 2.7).
- Virtual: toliqIsm, virtual populate buyurtmalar (Misol 1, 9, 2.8).
- Aggregation: top mijozlar, oylik savdo (Misol 4, 2.9).
- Transaction: buyurtma + zaxira (Misol 5, 2.11).
- Indeks + text search: mahsulot qidiruv (Misol 6, 2.12).
- 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!