Mongoose ODM + MongoDB — CRUD
Muammodan boshlaylik
TypeORM va Prisma darslari murakkab tuyulishi mumkin: migration fayllar, schema.prisma, adapter lar, entity dekoratorlar... MongoDB va Mongoose bilan bular yo'q.
Mongoose da siz faqat ikkita narsani yozasiz: Schema (ma'lumot qanday ko'rinishda bo'ladi) va Service (u bilan nima qilasiz). Hammasi shu.
Mongoose nima? MongoDB bilan farqi?
MongoDB — ma'lumotlar bazasi. U ma'lumotlarni JSON-ga o'xshash hujjatlar sifatida saqlaydi. Jadvallar yo'q — collection lar bor. Qatorlar yo'q — document lar bor.
PostgreSQL (jadval): MongoDB (collection):
┌────┬────────┬──────────────┐ { _id: "64a...", name: "Jasur",
│ id │ name │ email │ email: "jasur@gmail.com",
├────┼────────┼──────────────┤ age: 24 }
│ 1 │ Jasur │ j@gmail.com │
│ 2 │ Malika │ m@gmail.com │ { _id: "64b...", name: "Malika",
└────┴────────┴──────────────┘ email: "malika@gmail.com" }
Mongoose — MongoDB uchun ODM (Object Document Mapper). U MongoDB ning past darajali drayveridan ustida ishlaydi va qulay interfeys beradi: Schema, validatsiya, metodlar.
Hayotiy o'xshatma: MongoDB — xom ma'lumotlar ombori, Mongoose — omborchining yordamchisi. Yordamchi narsalarni tartibda saqlaydi, noto'g'ri narsa qo'ymoqchi bo'lsangiz to'xtatadi, kerak bo'lganda tez topib beradi.
Loyiha tuzilishi — toza arxitektura
Har bir resurs uchun alohida papka. Hamma narsa o'z joyida:
src/
├── app.module.ts
├── main.ts
│
└── users/ ← bitta resurs = bitta papka
├── users.module.ts ← modulni birlashtiradi
├── users.controller.ts ← HTTP so'rovlar
├── users.service.ts ← biznes mantiq
├── schemas/
│ └── user.schema.ts ← MongoDB schema
└── dto/
├── create-user.dto.ts ← yaratish uchun ma'lumot shakli
└── update-user.dto.ts ← yangilash uchun ma'lumot shakli
Bu tuzilish — NestJS da standart. Schema va DTO alohida papkalarda chunki ular har xil vazifani bajaradi: Schema — baza bilan gaplashadi, DTO — foydalanuvchi bilan.
O'rnatish
npm install @nestjs/mongoose mongoose
1. MongoDB ga ulanish — AppModule
// src/app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersModule } from './users/users.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/myapp'), // ← ulanish
UsersModule,
],
})
export class AppModule {}
Haqiqiy loyihada URL .env dan olinadi — bu keyingi darslarda ko'ramiz. Hozircha to'g'ridan-to'g'ri yozamiz.
2. Schema — ma'lumot qanday ko'rinishda bo'ladi
Schema — MongoDB collection da har bir document qanday tuzilishda bo'lishini belgilaydi. Mongoose shu sxemaga ko'ra validatsiya qiladi.
// src/users/schemas/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
// Document tipi — TypeScript uchun
export type UserDocument = HydratedDocument<User>;
@Schema({ timestamps: true }) // ← createdAt, updatedAt avtomatik qo'shiladi
export class User {
@Prop({ required: true })
name: string;
@Prop({ required: true, unique: true, lowercase: true })
email: string;
@Prop({ required: true, select: false }) // select: false — so'rovda ko'rsatilmaydi
password: string;
@Prop({ default: 0 })
age: number;
@Prop({ default: true })
isActive: boolean;
}
export const UserSchema = SchemaFactory.createForClass(User);
Dekoratorlar nima qiladi:
@Schema()— bu sinf MongoDB sxemasi ekanini bildiradi@Prop()— bu maydon sxemaga qo'shiladirequired: true— bo'sh bo'lmasligi kerakunique: true— takrorlanmasligi keraklowercase: true— saqlashdan oldin kichik harfga o'tkazadiselect: false—find()da parol avtomatik kelmaydi (xavfsizlik)timestamps: true—createdAtvaupdatedAtMongoose o'zi boshqaradi
3. DTO — foydalanuvchidan qabul qilinadigan ma'lumot shakli
DTO (Data Transfer Object) — foydalanuvchi yuboradigan ma'lumot qanday bo'lishi kerakligini belgilaydi. Schema emas, DTO validatsiya qiladi.
// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, IsInt, Min, Max, IsOptional, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
@IsOptional()
@IsInt()
@Min(1)
@Max(120)
age?: number;
}
// src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
// PartialType — CreateUserDto dagi barcha maydonlarni ixtiyoriy qiladi
export class UpdateUserDto extends PartialType(CreateUserDto) {}
PartialType — juda qulay. UpdateUserDto uchun alohida hamma narsani qayta yozmasiz — CreateUserDto dan meros olib, hammasini ? (ixtiyoriy) qiladi.
4. Module — hammasini birlashtirish
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User, UserSchema } from './schemas/user.schema';
@Module({
imports: [
// Sxemani shu modulga ro'yxatdan o'tkazamiz
MongooseModule.forFeature([
{ name: User.name, schema: UserSchema },
]),
],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // boshqa modullar ishlatishi uchun
})
export class UsersModule {}
5. Service — CRUD amallar
// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './schemas/user.schema';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectModel(User.name) private userModel: Model<UserDocument>,
) {}
// Yaratish
async create(dto: CreateUserDto): Promise<User> {
const user = new this.userModel(dto);
return user.save();
}
// Barchasini olish
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
// Bittasini ID bo'yicha olish
async findOne(id: string): Promise<User> {
const user = await this.userModel.findById(id).exec();
if (!user) {
throw new NotFoundException(`Foydalanuvchi topilmadi: ${id}`);
}
return user;
}
// Yangilash
async update(id: string, dto: UpdateUserDto): Promise<User> {
const user = await this.userModel
.findByIdAndUpdate(
id,
dto,
{ new: true }, // ← yangilangandan keyin yangi hujjatni qaytaradi
)
.exec();
if (!user) {
throw new NotFoundException(`Foydalanuvchi topilmadi: ${id}`);
}
return user;
}
// O'chirish
async remove(id: string): Promise<{ message: string }> {
const user = await this.userModel.findByIdAndDelete(id).exec();
if (!user) {
throw new NotFoundException(`Foydalanuvchi topilmadi: ${id}`);
}
return { message: `${user.name} o'chirildi` };
}
}
Muhim tushunchalar:
@InjectModel(User.name) — NestJS DI tizimiga "menga User sxemasining Mongoose Model ini ber" deydi. User.name — bu "User" degan string, sxema ro'yxatdan o'tkazilganda shu nom ishlatiladi.
{ new: true } — findByIdAndUpdate standart holda yangilanishdan oldingi hujjatni qaytaradi. new: true bilan yangilangandan keyin gi hujjat keladi. Bu ko'pincha unutiladi.
NotFoundException — NestJS ning o'rnatilgan Exception klassi. U 404 Not Found javobini avtomatik qaytaradi.
6. Controller — HTTP marshrutlash
// src/users/users.controller.ts
import {
Controller, Get, Post, Put, Delete,
Body, Param, HttpCode, HttpStatus,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
// POST /users
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
// GET /users
@Get()
findAll() {
return this.usersService.findAll();
}
// GET /users/:id
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
// PUT /users/:id
@Put(':id')
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
return this.usersService.update(id, dto);
}
// DELETE /users/:id
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
}
Controller faqat so'rovni qabul qiladi va Service ga uzatadi — boshqa hech narsa qilmaydi. Bu toza arxitekturaning asosiy qoidasi.
So'rov-javob oqimi — to'liq manzara
POST /users → Body: { name, email, password }
│
▼
UsersController.create(@Body() dto)
│ DTO validatsiya (class-validator)
▼
UsersService.create(dto)
│ new this.userModel(dto) → .save()
▼
MongoDB: users collection ga yozadi
│
▼
Javob: { _id, name, email, age, isActive, createdAt, updatedAt }
Mongoose metodlari — qisqa ma'lumotnoma
// Yaratish
new this.userModel(dto).save()
this.userModel.create(dto) // ikkalasi bir xil
// O'qish
this.userModel.find() // barchasi
this.userModel.find({ isActive: true }) // shartli
this.userModel.findById(id) // ID bo'yicha
this.userModel.findOne({ email }) // bitta, shartli
// Yangilash
this.userModel.findByIdAndUpdate(id, dto, { new: true })
this.userModel.findOneAndUpdate({ email }, dto, { new: true })
this.userModel.updateMany({ isActive: false }, { isActive: true })
// O'chirish
this.userModel.findByIdAndDelete(id)
this.userModel.deleteMany({ isActive: false })
// Foydali
this.userModel.countDocuments({ isActive: true }) // soni
this.userModel.exists({ email }) // bormi?
this.userModel.find().select('name email -password') // maydonlar
this.userModel.find().sort({ createdAt: -1 }) // tartiblash
this.userModel.find().limit(10).skip(20) // pagination
Tez-tez uchraydigan xatolar
{ new: true } unutish
// ❌ Yangilanishdan oldingi hujjat keladi — foydalanuvchi eski ma'lumot ko'radi
const user = await this.userModel.findByIdAndUpdate(id, dto).exec();
// ✅ Yangilangandan keyin yangi hujjat
const user = await this.userModel.findByIdAndUpdate(id, dto, { new: true }).exec();
Noto'g'ri MongoDB ID formati
// ❌ "abc" kabi noto'g'ri ID da CastError xatosi chiqadi — 500 javob
await this.userModel.findById('abc');
// ✅ Oldin tekshirish
import { isValidObjectId } from 'mongoose';
async findOne(id: string) {
if (!isValidObjectId(id)) {
throw new NotFoundException('Noto\'g\'ri ID format');
}
const user = await this.userModel.findById(id).exec();
if (!user) throw new NotFoundException('Topilmadi');
return user;
}
Parolni ochiq qaytarish
// Schema da select: false qo'yilgan — odatda kelmaydi
@Prop({ required: true, select: false })
password: string;
// Faqat kerak bo'lganda (masalan, login tekshiruvi) — '+' bilan so'raladi
const user = await this.userModel
.findOne({ email })
.select('+password')
.exec();
Xulosa
Mongoose — MongoDB bilan ishlashning eng qulay yo'li. Migration yo'q, alohida schema fayl yo'q, adapter yo'q. Faqat Schema va Service — boshqa hech narsa.
Toza arxitektura qoidasi:
Controller → faqat HTTP (so'rov qabul, javob yuborish)
Service → faqat biznes mantiq (CRUD, hisoblash)
Schema → faqat baza tuzilishi
DTO → faqat kiritma validatsiya
Har birining roli aniq, har biri o'z ishini qiladi — boshqasiga aralashmaydi.
Keyingi dars
23-dars: Database Relations (Mongoose) — ref, populate, virtual