TypeORM + PostgreSQL — CRUD

~30 daq · NestJS · Ma'lumotlar bazasi

Muammodan boshlaylik

Siz NestJS da loyiha yozyapsiz. Ma'lumotlar bazasiga ulanish kerak. Shoshib qolsangiz, shunday yozib qo'yishingiz mumkin:

const users = await connection.query(
  `SELECT * FROM users WHERE id = ${id}`
);

Bu ishlaydi. Lekin muammolar bor:

SQL Injection. Agar id foydalanuvchidan kelsa — xaker id o'rniga 1 OR 1=1 yubora oladi. Barcha foydalanuvchilar ma'lumoti ochilib ketadi.

Turlar yo'q. users — bu any[]. Ichida nima borligini TypeScript bilmaydi. Xato qilsangiz — kompilyator sezmaydi.

Takrorlanish. Har bir jadval uchun bir xil SQL yozasiz — INSERT, UPDATE, DELETE. Bu juda zerikarli.

Ma'lumotlar bazasiga bog'liqlik. MySQL dan PostgreSQL ga o'tmoqchi bo'lsangiz — barcha SQL ni qayta yozasiz.

Aynan shu muammolarni hal qilish uchun ORM kerak.


ORM nima?

ORM — Object Relational Mapping. U ma'lumotlar bazasi jadvallari va JavaScript ob'ektlari orasida ko'prik vazifasini o'taydi.

Hayotiy misol bilan tushuntirsak: Siz xorijiy mamlakatga bordingiz va til bilmaysiz. Tarjimon yollaysiz — u sizning gapingizni o'sha tilga tarjima qiladi, javobni esa sizga qaytaradi. ORM — aynan shu tarjimon. Siz TypeScript yozasiz — ORM uni SQL ga aylantiradi, natijani esa TypeScript ob'ektiga qaytaradi.

Siz (TypeScript) ORM (TypeORM) PostgreSQL (DB)
user.save() → INSERT INTO users... → Saqlash
findOne({id}) → SELECT * FROM users... → Ma'lumot olish
user.remove() → DELETE FROM users... → O'chirish

TypeORM nima?

TypeORM — JavaScript va TypeScript uchun eng mashhur ORM lardan biri. Nomi ham aytib turibdi — Type + ORM. TypeScript bilan mukammal ishlaydi.

TypeORM quyidagi ma'lumotlar bazalarini qo'llab-quvvatlaydi:

  • PostgreSQL — eng kuchli, eng ko'p ishlatiladigan
  • MySQL va MariaDB
  • SQLite
  • Microsoft SQL Server
  • Oracle

Shu bitta kutubxona — bir nechta ma'lumotlar bazasi. Bugun PostgreSQL ishlatyapsiz, ertaga boshqasiga o'tmoqchi bo'lsangiz — kodni deyarli o'zgartirmasdan o'tish mumkin.


Nima uchun PostgreSQL?

MySQL bilan solishtirish:

Mezon PostgreSQL MySQL
JSON qo'llab-quvvatlash Kuchli — JSONB Oddiy
Murakkab so'rovlar Juda kuchli O'rtacha
Full-text qidirish Ichki Qo'shimcha kerak
Kengaytirish PostGIS, uuid va h.k. Cheklangan
Litsenziya Bepul, ochiq Oracle tomonida

PostgreSQL — enterprise darajadagi loyihalarda eng ko'p tanlaniladigan ma'lumotlar bazasi. Supabase, Vercel, Heroku — barchasi PostgreSQL ni tavsiya qiladi.


O'rnatish

npm install @nestjs/typeorm typeorm pg

Uchta paket o'rnatildi. Har birining roli bor:

typeorm — asosiy ORM kutubxona. SQL yaratish, entity boshqarish, migration — hammasi shu yerda.

pg — PostgreSQL bilan gaplasha oladigan quyi darajali driver. TypeORM PostgreSQL bilan to'g'ridan-to'g'ri gaplasha olmaydi — pg orqali gaplashadi. MySQL da buning o'rniga mysql2 ishlatiladi.

@nestjs/typeorm — NestJS va TypeORM ni birlashtiruvchi ko'prik. TypeOrmModule ni beradi — NestJS ning modul tizimiga TypeORM ni ulash uchun.

💡 Uchala paket birga o'rnatilishi shart. pg ni unutib qoldirsangiz — ulanish paytida xato beradi.


PostgreSQL ga ulanish

app.module.ts — bu NestJS loyihasining asosiy moduli. Shu yerda TypeORM ga "qaysi ma'lumotlar bazasiga ulan" deb aytamiz.

Asosiy usul — forRoot

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',           // ma'lumotlar bazasi turi
      host: 'localhost',          // server manzili
      port: 5432,                 // PostgreSQL standart port (MySQL — 3306)
      username: 'your_username',  // foydalanuvchi nomi
      password: 'your_password',  // parol
      database: 'your_database',  // baza nomi
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
})
export class AppModule {}

Har bir maydonni tushuntiramiz:

type: 'postgres' — qaysi ma'lumotlar bazasi ishlatilishini aytamiz. TypeORM bir nechta bazani qo'llab-quvvatlaydi — 'mysql', 'sqlite', 'mssql' va boshqalar. Biz PostgreSQL ishlatamiz.

host: 'localhost' — PostgreSQL server qayerda ishlayapti. Hozir kompyuteringizda o'rnatilgan — shuning uchun localhost. Production da server IP manzili yoki domen bo'ladi, masalan db.myapp.com.

port: 5432 — PostgreSQL standart port. MySQL da 3306 edi — PostgreSQL da 5432.

entities: [__dirname + '/**/*.entity{.ts,.js}'] — TypeORM qaysi fayllar jadval tavsifi ekanini bilishi kerak. Bu qator "istalgan papkada .entity.ts yoki .entity.js bilan tugaydigan barcha fayllarni ol" degani. Yangi entity yaratganda bu yerga qo'shib yurish shart emas — avtomatik topiladi.

synchronize: true — bu sozlama TypeORM ga "entity sinflarimdagi o'zgarishlarni ma'lumotlar bazasiga avtomatik aks ettir" deydi. Yangi ustun qo'shdingizmi — jadvalga avtomatik qo'shiladi. Ilovani ishga tushirganda jadval yo'q bo'lsa — yaratiladi.

⚠️ synchronize: true faqat development uchun. Production da bu sozlamani hech qachon yoqmang. Nima uchun xavfli? Entity da biror ustunni o'chirib yuborsangiz — production bazadagi o'sha ustundagi barcha ma'lumotlar yo'qoladi. Production da migration ishlatiladi — bu keyingi darslarda ko'riladi.


Advanced: .env fayl bilan ulanish

Yuqoridagi usulda parol va baza nomi to'g'ridan-to'g'ri kod ichida yozilgan. Bu ikkita muammo:

  1. Xavfsizlik — kodni GitHub ga push qilsangiz parolingiz ochiq ko'rinadi
  2. Moslashuvchanlik — development va production uchun turli sozlamalar kerak, lekin kod bir xil

Yechim — sozlamalarni .env faylga ko'chirish.

1. .env fayl yarating:

# .env  ← bu fayl .gitignore da bo'ladi, GitHub ga chiqmaydi
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=12345
DB_NAME=mydb
NODE_ENV=development

2. ConfigModule o'rnating:

npm install @nestjs/config

ConfigModule — .env faylni o'qiydi va barcha qiymatlarni dastur ichida ishlatish mumkin qiladi.

3. app.module.ts ni yangilang:

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    // 1. ConfigModule — .env faylni o'qiydi
    ConfigModule.forRoot({ isGlobal: true }),

    // 2. TypeOrmModule — ConfigService tayyor bo'lgach ulaydi
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('DB_HOST', 'localhost'),
        port: configService.get<number>('DB_PORT', 5432),
        username: configService.get('DB_USERNAME', 'postgres'),
        password: configService.get('DB_PASSWORD', ''),
        database: configService.get('DB_NAME', 'mydb'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: configService.get('NODE_ENV') !== 'production',
        logging: configService.get('NODE_ENV') === 'development',
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

forRootAsync — "forRoot" ning .env bilan ishlaydigan versiyasi. Nima uchun alohida usul kerak? Chunki oddiy forRoot da ConfigService ishlatib bo'lmaydi — u hali yuklangan bo'lmaydi. forRootAsync esa "ConfigService tayyor bo'lgandan keyingina ishga tush" deydi.

configService.get('DB_HOST', 'localhost') — .env dan DB_HOST ni o'qiydi. Ikkinchi argument standart qiymat — agar .env da topilmasa 'localhost' ishlatiladi.

configService.get<number>('DB_PORT', 5432) — <number> qo'shilgan, chunki .env dan hamma narsa string keladi. TypeScript ga "bu aslida son" deb aytamiz.

synchronize: configService.get('NODE_ENV') !== 'production' — development da true, production da false. Shart shu yerda avtomatik hal qilinadi.

logging: configService.get('NODE_ENV') === 'development' — development da TypeORM har bir SQL ni konsolda ko'rsatadi. Debug uchun juda foydali.

💡 .env faylni .gitignore ga qo'shing. Repository ga esa .env.example ni qo'shing — boshqa dasturchilarga qanday o'zgaruvchilar kerakligini ko'rsatish uchun:

# .env.example — bu fayl GitHub da ko'rinadi, qiymatsiz
DB_HOST=
DB_PORT=
DB_USERNAME=
DB_PASSWORD=
DB_NAME=
NODE_ENV=

Entity — jadval tavsifi

Entity — ma'lumotlar bazasidagi jadvalning TypeScript ko'rinishi.

Tasavvur qiling: PostgreSQL da users jadvali bor — ichida id, name, email ustunlari bor. Entity — xuddi shu jadvalning TypeScript sinfi.

// users/entities/user.entity.ts
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  DeleteDateColumn,
} from 'typeorm';

@Entity('users')
export class User {

  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  name: string;

  @Column({ unique: true })
  email: string;

  @Column({ select: false })
  password: string;

  @Column({
    type: 'enum',
    enum: ['user', 'moderator', 'admin'],
    default: 'user',
  })
  role: string;

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @DeleteDateColumn()
  deletedAt: Date;
}

Har bir dekoratorni tushuntirамиз:

@Entity('users') — "bu sinf users jadvaliga mos keladi" degani. synchronize: true bo'lganda jadval yo'q bo'lsa yaratadi, ustunlar o'zgarganda yangilaydi.

@PrimaryGeneratedColumn() — asosiy kalit. PostgreSQL da SERIAL PRIMARY KEY — har yangi yozuv qo'shilganda avtomatik 1, 2, 3... ketadi. Siz qiymat berish shart emas.

@Column({ length: 100 }) — PostgreSQL da VARCHAR(100) bo'ladi. Ko'pi bilan 100 ta belgi.

@Column({ unique: true }) — bir xil email ikki marta bo'lmasin degan cheklash. Ma'lumotlar bazasi darajasida tekshiriladi.

@Column({ select: false }) — bu muhim xavfsizlik sozlamasi. findOne() yoki find() chaqirsangiz — password ustuni natijada kelmaydi. Parolni tasodifan response ga yuborib qo'ymaslik uchun. Agar parol kerak bo'lsa — maxsus usul bilan olish mumkin (pastda ko'ramiz).

@Column({ type: 'enum' }) — PostgreSQL da haqiqiy ENUM turi. Faqat 'user', 'moderator', 'admin' dan biri kirishi mumkin — boshqa qiymat kiritishga urinsa ma'lumotlar bazasi rad etadi.

@CreateDateColumn() — yozuv yaratilganda TypeORM o'zi hozirgi vaqtni yozadi. Siz berish shart emas.

@UpdateDateColumn() — yozuv yangilanganda TypeORM o'zi hozirgi vaqtni yozadi.

@DeleteDateColumn() — Soft Delete uchun. Qachon ishlashi keyinroq tushuntiriladi.


DTO — kiruvchi ma'lumotlarni tekshirish

DTO (Data Transfer Object) — foydalanuvchi yuborgan ma'lumotni tekshirish uchun sinf.

Tasavvur qiling: foydalanuvchi POST /users ga so'rov yuboryapti. U name o'rniga raqam yuborsa, yoki email o'rniga tasodifiy matn yubborsa — ma'lumotlar bazasiga yozib qo'ymaslik kerak. DTO bu tekshiruvni avtomatik bajaradi.

// users/dto/create-user.dto.ts
import { IsString, IsEmail, IsEnum, MinLength, MaxLength, IsOptional } from 'class-validator';

export enum UserRole {
  USER = 'user',
  MODERATOR = 'moderator',
  ADMIN = 'admin',
}

export class CreateUserDto {

  @IsString()
  @MinLength(2, { message: 'Ism kamida 2 ta belgi bo\'lishi kerak' })
  @MaxLength(100)
  name: string;

  @IsEmail({}, { message: 'Email noto\'g\'ri formatda' })
  email: string;

  @IsString()
  @MinLength(8, { message: 'Parol kamida 8 ta belgi bo\'lishi kerak' })
  password: string;

  @IsEnum(UserRole)
  @IsOptional()
  role?: UserRole;
}
// users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
// PartialType — CreateUserDto dagi barcha maydonlarni ixtiyoriy (optional) qiladi
// Yangilashda faqat o'zgartirmoqchi bo'lgan maydonlarni yuborish mumkin

Module va Repository ulanishi

TypeORM da ma'lumotlar bazasiga murojaat Repository orqali bo'ladi. Repository — "omborchi" — jadval bilan ishlash uchun tayyor metodlar to'plami.

Avval modulga entity ni ro'yxatdan o'tkazamiz:

// users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  // forFeature — "bu modulda User entity si ishlatiladi" degani
  // NestJS UserRepository ni dependency injection uchun tayyorlaydi
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

forRoot va forFeature farqi:

  • forRoot — bir marta, AppModule da. Butun dastur uchun ulanish sozlamasi.
  • forFeature — har bir modulda alohida. "Mana bu entity lar shu modulda ishlatiladi" degani.

Endi service da repository ni olamiz:

// users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UsersService {

  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  // CRUD metodlar pastda...
}

@InjectRepository(User) — NestJS ga "menga User entity sining repository sini ber" deydi. NestJS o'zi yaratib inject qiladi. Siz new Repository() yozish shart emas.

Repository<User> — bu TypeScript generik. <User> qismi TypeScript ga "bu repository User ob'ektlari bilan ishlaydi" deb aytadi. Shu sababli find() qaytarganda tip User[] bo'ladi — any[] emas.


CRUD operatsiyalar

CREATE — yangi foydalanuvchi yaratish

async create(createUserDto: CreateUserDto): Promise<User> {

  // 1. Email allaqachon bormi tekshiramiz
  const exists = await this.userRepository.findOne({
    where: { email: createUserDto.email },
  });

  if (exists) {
    throw new ConflictException('Bu email allaqachon ro\'yxatdan o\'tgan');
    // 409 Conflict — HTTP status
  }

  // 2. Parolni hash qilamiz — hech qachon ochiq parol saqlanmaydi
  const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
  // 10 — salt rounds. Qancha ko'p bo'lsa xavfsizroq, lekin sekinroq.
  // Production da 10-12 oralig'i optimal

  // 3. Entity ob'ektini yaratamiz
  const user = this.userRepository.create({
    ...createUserDto,
    password: hashedPassword,
  });
  // create() — JavaScript ob'ekti yaratadi, hali bazaga YOZILMADI

  // 4. Bazaga saqlaymiz
  return await this.userRepository.save(user);
  // save() — INSERT INTO users (...) VALUES (...) SQL ni bajaradi
}

create() va save() — nima uchun ikkita qadam?

create() faqat TypeScript ob'ekti yaratadi. Bazaga hech narsa yozilmaydi. Shu bosqichda ob'ektga qo'shimcha ma'lumot qo'shishingiz, tekshirishingiz mumkin.

save() bazaga yozadi. Ob'ekt yangi bo'lsa — INSERT, mavjud (id si bor) bo'lsa — UPDATE.

READ — ma'lumot olish

// Barcha foydalanuvchilar — sahifalash bilan
async findAll(page: number = 1, limit: number = 10) {

  const [data, total] = await this.userRepository.findAndCount({
    where: { isActive: true },
    order: { createdAt: 'DESC' },
    skip: (page - 1) * limit,  // OFFSET — nechta yozuv o'tkazib yuboriladi
    take: limit,               // LIMIT — nechta yozuv olinadi
  });
  // findAndCount — [yozuvlar massivi, umumiy son] ni birgalikda qaytaradi
  // Ikkita so'rov emas — bitta SQL: SELECT ... LIMIT ... + COUNT(*)

  return {
    data,
    total,
    page,
    lastPage: Math.ceil(total / limit),
  };
}

// Bitta foydalanuvchi — ID bo'yicha
async findOne(id: number): Promise<User> {
  const user = await this.userRepository.findOne({
    where: { id },
  });

  if (!user) {
    throw new NotFoundException(`${id}-foydalanuvchi topilmadi`);
    // 404 Not Found — HTTP status
  }

  return user;
}

// Email bo'yicha — parol bilan birga (login uchun)
async findByEmailWithPassword(email: string): Promise<User | null> {
  return await this.userRepository
    .createQueryBuilder('user')
    .addSelect('user.password')
    // select: false bo'lganda parol kelmaydi
    // addSelect — maxsus so'rov bilan parolni ham olish
    .where('user.email = :email', { email })
    // :email — placeholder. SQL Injection dan himoya
    .getOne();
}

UPDATE — yangilash

async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {

  // 1. Foydalanuvchi borligini tekshiramiz
  const user = await this.findOne(id);
  // findOne ichida NotFoundException bor — topilmasa xato otadi

  // 2. Agar parol kelgan bo'lsa — hash qilamiz
  if (updateUserDto.password) {
    updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
  }

  // 3. Kelgan maydonlarni user ob'ektiga qo'shamiz
  Object.assign(user, updateUserDto);
  // Bu degani: kelgan har bir maydon user ga yoziladi
  // Kelmagan maydonlar o'zgarmaydi — PartialType shuning uchun kerak

  // 4. Saqlaymiz
  return await this.userRepository.save(user);
  // id si bor ob'ekt — UPDATE INTO users SET ... WHERE id = ?
}

DELETE — o'chirish

Real loyihalarda ikki xil o'chirish bo'ladi:

Soft Delete — aslida o'chirilmaydi. deletedAt ustuniga hozirgi vaqt yoziladi. find() da ko'rinmaydi, lekin bazada bor. Keyinchalik tiklash mumkin.

Hard Delete — haqiqatan o'chiriladi. DELETE SQL bajariladi. Tiklab bo'lmaydi.

// Soft Delete — production da shu ishlatiladi
async remove(id: number): Promise<void> {
  const user = await this.findOne(id);
  await this.userRepository.softRemove(user);
  // deletedAt = NOW() — bazadan o'chirilmadi, faqat sana yozildi
  // Keyingi find() da bu yozuv ko'rinmaydi — TypeORM WHERE deletedAt IS NULL qo'shadi
}

// Tiklash — soft delete orqasini qaytarish
async restore(id: number): Promise<User> {
  await this.userRepository.restore(id);
  // deletedAt = NULL — yana ko'rinadigan bo'ldi
  return await this.findOne(id);
}

// Hard Delete — faqat zarur hollarda
async hardRemove(id: number): Promise<void> {
  const user = await this.findOne(id);
  await this.userRepository.remove(user);
  // DELETE FROM users WHERE id = ?
}

Soft Delete nima uchun kerak? Tasavvur qiling:

  • Foydalanuvchi akkountini o'chirdi — lekin uning xarid tarixi, sharhlari hali ham boshqa jadvalda turibdi. Hard delete qilsangiz — o'sha joylar "foydalanuvchi topilmadi" deb ishlamay qoladi.
  • Xaker kimningdir akkountini o'chirdi — Soft Delete bilan tiklash mumkin. Hard Delete — yo'qoldi.
  • Audit — kim qachon o'chirilganini bilish kerak. Soft Delete deletedAt vaqtini saqlaydi.

QueryBuilder — murakkab so'rovlar

Oddiy find() yetarli bo'lmagan holatlar uchun TypeORM da QueryBuilder bor:

// Qidiruv, filtirlash, sahifalash — birgalikda
async search(params: {
  query?: string;
  role?: string;
  page: number;
  limit: number;
}) {

  const qb = this.userRepository.createQueryBuilder('user');
  // 'user' — alias, SQL da AS user degan ma'noda

  if (params.query) {
    qb.andWhere(
      'user.name ILIKE :query OR user.email ILIKE :query',
      { query: `%${params.query}%` },
    );
    // ILIKE — PostgreSQL ning katta-kichik harfga sezgir bo'lmagan LIKE
    // MySQL da faqat LIKE bor — katta-kichik harfga sezgir
  }

  if (params.role) {
    qb.andWhere('user.role = :role', { role: params.role });
  }

  const [data, total] = await qb
    .orderBy('user.createdAt', 'DESC')
    .skip((params.page - 1) * params.limit)
    .take(params.limit)
    .getManyAndCount();

  return { data, total };
}

Controller

// users/users.controller.ts
import {
  Controller, Get, Post, Put, Delete, Patch,
  Param, Body, Query, ParseIntPipe, DefaultValuePipe,
  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()
  @HttpCode(HttpStatus.CREATED)   // 201 qaytaradi — 200 emas
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    // DefaultValuePipe — page kelmasa 1 qo'yadi
    // ParseIntPipe — string '1' ni son 1 ga aylantiradi
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
  ) {
    return this.usersService.findAll(page, limit);
  }

  @Get('search')
  // MUHIM: bu route ':id' dan YUQORIDA bo'lishi kerak
  // Aks holda 'search' so'zi id sifatida qabul qilinadi
  search(
    @Query('q') query: string,
    @Query('role') role: string,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
  ) {
    return this.usersService.search({ query, role, page, limit });
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  @Put(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateUserDto: UpdateUserDto,
  ) {
    return this.usersService.update(id, updateUserDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)  // 204 — o'chirildi, body yo'q
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id);
  }

  @Patch(':id/restore')
  restore(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.restore(id);
  }
}

Loyiha tuzilishi

src/
├── app.module.ts             ← TypeOrmModule.forRootAsync() — bir marta
└── users/
    ├── dto/
    │   ├── create-user.dto.ts
    │   └── update-user.dto.ts
    ├── entities/
    │   └── user.entity.ts    ← @Entity, @Column, @PrimaryGeneratedColumn
    ├── users.controller.ts   ← HTTP so'rovlarni qabul qiladi
    ├── users.service.ts      ← Biznes logika, Repository bilan ishlash
    └── users.module.ts       ← TypeOrmModule.forFeature([User])

So'rov kelganda nima bo'ladi?

POST /users so'rovi kelsa:

  1. UsersController.create() so'rovni qabul qiladi
  2. ValidationPipe DTO ni tekshiradi — name, email, password to'g'rimi
  3. UsersService.create() ga ma'lumot uzatiladi
  4. Email tekshiriladi, parol hash qilinadi
  5. userRepository.save() orqali PostgreSQL ga yoziladi
  6. INSERT INTO users (...) VALUES (...) RETURNING * SQL bajariladi
  7. Yangi yaratilgan foydalanuvchi JSON javob sifatida qaytadi

Vazifalar

1-vazifa — Asosiy: Product entity yozing: id, name, price (decimal), stock (integer, default: 0), isAvailable (boolean, default: true), createdAt. So'ng shu entity uchun to'liq CRUD yozing.

2-vazifa — O'ylash: synchronize: true production da nima uchun xavfli? Quyidagi holatni o'ylab ko'ring: entity da name ustunini o'chirdingiz — production da nima bo'ladi?

3-vazifa — Qo'shimcha: findAll metodiga filtirlash qo'shing: isAvailable=true yoki false bo'yicha filtrlash. URL: GET /products?available=true


Xulosa

forRoot vs forRootAsync:

  • forRoot — qiymatlar to'g'ridan-to'g'ri yoziladi, boshqa servislar ishlatilmaydi
  • forRootAsync — ConfigService kabi boshqa servislarni kutib, so'ng sozlamalar olinadi

forRootAsync ning uch qismi:

  • imports — qaysi modullar tayyor bo'lishi kerak
  • useFactory — sozlamalarni qaytaruvchi funksiya
  • inject — useFactory ga argument sifatida nima berilishi

Eslab qoling:

  • synchronize: true — faqat development da
  • select: false — parolni yashirish uchun
  • create() + save() — INSERT
  • save() + mavjud ob'ekt — UPDATE
  • softRemove() — Soft Delete, remove() — Hard Delete
  • ILIKE — PostgreSQL da katta-kichik harfga sezgir bo'lmagan qidirish

Keyingi dars

17-dars: TypeORM — Jadvallar orasidagi bog'liqliklar (Relations)