TypeORM + PostgreSQL — CRUD
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.
pgni 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: truefaqat 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 damigrationishlatiladi — 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:
- Xavfsizlik — kodni GitHub ga push qilsangiz parolingiz ochiq ko'rinadi
- 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.
💡
.envfaylni.gitignorega qo'shing. Repository ga esa.env.exampleni 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,AppModuleda. 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
deletedAtvaqtini 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:
UsersController.create()so'rovni qabul qiladiValidationPipeDTO ni tekshiradi —name,email,passwordto'g'rimiUsersService.create()ga ma'lumot uzatiladi- Email tekshiriladi, parol hash qilinadi
userRepository.save()orqali PostgreSQL ga yoziladiINSERT INTO users (...) VALUES (...) RETURNING *SQL bajariladi- 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 ishlatilmaydiforRootAsync—ConfigServicekabi boshqa servislarni kutib, so'ng sozlamalar olinadi
forRootAsync ning uch qismi:
imports— qaysi modullar tayyor bo'lishi kerakuseFactory— sozlamalarni qaytaruvchi funksiyainject—useFactoryga argument sifatida nima berilishi
Eslab qoling:
synchronize: true— faqat development daselect: false— parolni yashirish uchuncreate()+save()— INSERTsave()+ mavjud ob'ekt — UPDATEsoftRemove()— Soft Delete,remove()— Hard DeleteILIKE— PostgreSQL da katta-kichik harfga sezgir bo'lmagan qidirish
Keyingi dars
17-dars: TypeORM — Jadvallar orasidagi bog'liqliklar (Relations)