Serialization — response filtering, parolni yashirish
Muammodan boshlaylik
GET /users endpointi javob qaytardi:
{
"_id": "64a...",
"name": "Jasur",
"email": "jasur@gmail.com",
"password": "$2b$10$xK9mP2vL...",
"refreshToken": "$2b$10$aL7nQ3...",
"deletedAt": null,
"__v": 0
}
Hash qilingan bo'lsa ham — parol va refresh token javobda bo'lmasligi kerak. __v, deletedAt ham foydalanuvchiga kerak emas.
Har endpointda delete user.password yozish — takroriy va unutilishi mumkin. Serialization bu muammoni markaziy hal qiladi.
Serialization nima?
Serialization — ob'ektni javob (JSON) ga aylantirish jarayoni. NestJS da bu ClassSerializerInterceptor orqali boshqariladi.
Siz @Exclude() va @Expose() dekoratorlar bilan qaysi maydonlar ko'rinishini belgilaysiz — bir marta yoziladi, hamma joyda ishlaydi.
O'rnatish
npm install class-transformer
class-validator bilan birga o'rnatilgan bo'lishi mumkin — tekshiring.
Response DTO — nima qaytarilishini belgilash
// src/users/dto/user-response.dto.ts
import { Exclude, Expose, Transform } from 'class-transformer';
export class UserResponseDto {
@Expose()
id: string; // _id o'rniga id
@Expose()
name: string;
@Expose()
email: string;
@Expose()
role: string;
@Expose()
photo: string | null;
@Expose()
createdAt: Date;
// Bu maydonlar javobda KO'RSATILMAYDI
@Exclude()
password: string;
@Exclude()
refreshToken: string;
@Exclude()
deletedAt: Date;
@Exclude()
__v: number;
constructor(partial: Partial<UserResponseDto>) {
Object.assign(this, partial);
}
}
Global Interceptor ulash
// src/main.ts
import { NestFactory, Reflector } from '@nestjs/core';
import { ClassSerializerInterceptor } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global — barcha controller larda ishlaydi
app.useGlobalInterceptors(
new ClassSerializerInterceptor(app.get(Reflector)),
);
await app.listen(3000);
}
Service da DTO qaytarish
// src/users/users.service.ts
import { plainToInstance } from 'class-transformer';
import { UserResponseDto } from './dto/user-response.dto';
async findAll(): Promise<UserResponseDto[]> {
const users = await this.userModel.find({ deletedAt: null }).exec();
// plainToInstance — oddiy ob'ektni DTO klassiga aylantiradi
return plainToInstance(UserResponseDto, users.map(u => u.toObject()), {
excludeExtraneousValues: true, // ← @Expose() yo'q maydonlar o'tkazilmaydi
});
}
async findOne(id: string): Promise<UserResponseDto> {
const user = await this.userModel.findById(id).exec();
if (!user) throw new NotFoundException('Topilmadi');
return plainToInstance(UserResponseDto, user.toObject(), {
excludeExtraneousValues: true,
});
}
excludeExtraneousValues: true — @Expose() yo'q barcha maydonlar avtomatik tashlab yuboriladi. Bu eng xavfsiz variant.
MongoDB _id ni id ga aylantirish
MongoDB da _id ishlatiladi. Frontendga id qaytarish qulay:
// user-response.dto.ts
import { Expose, Transform } from 'class-transformer';
import { Types } from 'mongoose';
export class UserResponseDto {
@Expose()
@Transform(({ obj }) => obj._id?.toString()) // ← _id → id string
id: string;
@Expose()
name: string;
// ...
}
Yoki sxemada virtual qo'shish:
// user.schema.ts
@Schema({ timestamps: true, toJSON: { virtuals: true } })
export class User { ... }
UserSchema.virtual('id').get(function () {
return this._id.toHexString(); // ← _id → id
});
@SerializeOptions — controller darajasida sozlash
Ba'zan turli endpointlar uchun turli javob kerak. Admin uchun batafsil, oddiy user uchun kam:
// src/users/dto/user-admin-response.dto.ts
export class UserAdminResponseDto extends UserResponseDto {
@Expose()
deletedAt: Date | null; // admin ko'radi
@Expose()
provider: string;
@Expose()
isActive: boolean;
}
// users.controller.ts
import { SerializeOptions } from '@nestjs/common';
@Controller('users')
export class UsersController {
// Oddiy foydalanuvchi uchun
@Get(':id')
@SerializeOptions({ type: UserResponseDto })
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
// Admin uchun — ko'proq ma'lumot
@Get(':id/admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@SerializeOptions({ type: UserAdminResponseDto })
findOneAdmin(@Param('id') id: string) {
return this.usersService.findOneAdmin(id);
}
}
Tez-tez uchraydigan xatolar
toObject() chaqirmaslik
// ❌ Mongoose hujjati — class-transformer uni to'g'ri o'qiy olmaydi
const user = await this.userModel.findById(id).exec();
return plainToInstance(UserResponseDto, user); // ishlashi mumkin, lekin noaniq
// ✅ plain JavaScript ob'ektiga aylantirish
return plainToInstance(UserResponseDto, user.toObject(), {
excludeExtraneousValues: true,
});
excludeExtraneousValues unutish
// ❌ Barcha maydonlar o'tadi — @Exclude() bo'lganlar ham
plainToInstance(UserResponseDto, user.toObject())
// ✅ Faqat @Expose() bo'lganlar o'tadi
plainToInstance(UserResponseDto, user.toObject(), {
excludeExtraneousValues: true,
})
Massivni aylantirmaslik
// ❌ Massiv uchun alohida qayta ishlash kerak
const users = await this.userModel.find().exec();
return plainToInstance(UserResponseDto, users); // ← users Mongoose hujjatlar massivi
// ✅
return plainToInstance(
UserResponseDto,
users.map(u => u.toObject()), // ← har birini plain ob'ektga
{ excludeExtraneousValues: true },
);
Xulosa
Serialization — javobdan keraksiz maydonlarni bir martalik deklaratsiya bilan olib tashlash. @Exclude(), @Expose(), plainToInstance — uch narsa.
Eslab qoling:
@Exclude()— bu maydon javobda ko'rsatilmaydi@Expose()— bu maydon ko'rsatiladi (excludeExtraneousValues: truebilan)plainToInstance(Dto, obj, { excludeExtraneousValues: true })— o'girishuser.toObject()— Mongoose hujjatini plain JS ga aylantirishClassSerializerInterceptorglobal — bir marta ulash yetarli@SerializeOptions({ type: Dto })— controller darajasida DTO tanlash
Keyingi dars
30-dars: File Upload va Static Files