Custom Decorators
Decorator nima va nima uchun kerak?
NestJS da siz allaqachon ko'p decorator ishlatgansiz: @Controller(), @Get(), @UseGuards(), @Body(). Bular NestJS ning o'zi bergan decoratorlar.
Lekin ba'zan o'zingizning decorator ni yozish kerak bo'ladi. Nima uchun?
Tasavvur qiling: har bir endpointda hozirgi foydalanuvchini olish kerak.
// Har safar shu kodni yozasiz
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@Req() req: any) {
const user = req.user; // har safar shu
return user;
}
@Get('orders')
@UseGuards(JwtAuthGuard)
getOrders(@Req() req: any) {
const user = req.user; // yana shu
return user.orders;
}
req.user ni har safar yozish — takroriy va xatoga moyil. Buning o'rniga @CurrentUser() decorator yozsangiz:
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: User) {
return user; // toza, aniq
}
Mana shu — custom decorator ning asosiy sababi: takroriy kodni bir joyga yig'ish.
Param Decorator — createParamDecorator
Param decorator — metod parametrlariga qo'yiladigan decorator. @Body(), @Param(), @Req() kabilar shu turga kiradi.
createParamDecorator funksiyasi bilan yaratiladi:
// common/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
Ikki parametr:
data— decorator ga berilgan argument.@CurrentUser('email')desangiz,data = 'email'bo'ladi.ctx—ExecutionContext. Guard dagi kabi — so'rov haqida hamma ma'lumot shu yerda.
Foydalanish
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import { User } from './user.entity';
@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
@Get('profile')
getProfile(@CurrentUser() user: User) {
return user;
}
@Get('email')
getEmail(@CurrentUser() user: User) {
return { email: user.email };
}
}
data parametridan foydalanish
Decorator ga argument berib, foydalanuvchining faqat kerakli maydonini olish mumkin:
// common/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(field: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
// field berilgan bo'lsa — faqat o'sha maydonni qaytaradi
// berilmagan bo'lsa — butun user ob'ektini qaytaradi
return field ? user?.[field] : user;
},
);
@Get('profile')
getProfile(@CurrentUser() user: User) {
return user; // { id: 1, email: '...', role: 'admin' }
}
@Get('me/id')
getId(@CurrentUser('id') id: number) {
return { id }; // faqat id
}
@Get('me/role')
getRole(@CurrentUser('role') role: string) {
return { role }; // faqat role
}
Method va Class Decorator — SetMetadata
Ba'zan endpointga ma'lumot "yopishtirib" qo'yish kerak bo'ladi — Guard yoki Interceptor keyinchalik o'sha ma'lumotni o'qisin deb. Buning uchun SetMetadata ishlatiladi.
9-darsda @Roles() decorator ni ko'rgan edik. Xuddi shu usul:
// common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// common/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Public = () => SetMetadata('isPublic', true);
Bu decoratorlar hech narsa qaytarmaydi — ular faqat metadata yozadi. Reflector orqali Guard yoki Interceptor ichida o'qiladi.
Bir nechta decoratorni birlashtirish — applyDecorators
Bir endpointda bir xil decorator kombinatsiyasi qayta-qayta ishlatilsa — ularni bitta decorator ga birlashtirish mumkin.
Masalan, @UseGuards(JwtAuthGuard) va @ApiBearerAuth() (Swagger) har safar birga yoziladi:
// common/decorators/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
import { Roles } from './roles.decorator';
export function Auth(...roles: string[]) {
return applyDecorators(
Roles(...roles),
UseGuards(JwtAuthGuard, RolesGuard),
);
}
Endi controller da:
// Oldin — har safar ikkita decorator
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
remove(@Param('id') id: number) { ... }
// Keyin — bitta decorator
@Auth('admin')
@Delete(':id')
remove(@Param('id') id: number) { ... }
applyDecorators ichidagi barcha decoratorlar ketma-ket qo'llaniladi — natija bir xil, kod esa toza.
Tez-tez uchraydigan xatolar
1. request.user mavjud emas
export const CurrentUser = createParamDecorator(
(data, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user; // undefined bo'lishi mumkin!
},
);
Sabab: @CurrentUser() ishlatilgan endpointda JwtAuthGuard (yoki boshqa guard) qo'yilmagan. Guard request.user ga yozadi — guard bo'lmasa, request.user undefined.
// ❌ Guard yo'q — request.user = undefined
@Get('profile')
getProfile(@CurrentUser() user: User) {
return user; // undefined
}
// ✅ Guard bor — request.user to'ldirilgan
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: User) {
return user; // { id: 1, email: '...' }
}
2. applyDecorators ichida @ belgisi
// ❌ Xato — applyDecorators ichida @ ishlatilmaydi
export function Auth() {
return applyDecorators(
@UseGuards(JwtAuthGuard), // SyntaxError!
);
}
// ✅ To'g'ri — @ belgisisiz
export function Auth() {
return applyDecorators(
UseGuards(JwtAuthGuard),
);
}
applyDecorators ichida decorator funksiya sifatida chaqiriladi — @ belgisi faqat class/method ustida ishlatiladi.
3. SetMetadata kalitini noto'g'ri o'qish
// Decorator
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// Guard — kalit noto'g'ri yozilgan
const roles = this.reflector.get('role', ctx.getHandler()); // 'role' emas, 'roles'!
Yechim: Kalit nomini konstanta sifatida saqlang:
// common/decorators/roles.decorator.ts
export const ROLES_KEY = 'roles'; // bir joyda saqlang
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// Guard ichida
const roles = this.reflector.get(ROLES_KEY, ctx.getHandler()); // xato bo'lmaydi
Amaliy misol — to'liq decorator to'plami
Real loyihada odatda shu decoratorlar kerak bo'ladi:
// common/decorators/index.ts — hammasini bir joydan export qilish
// 1. Hozirgi foydalanuvchi
export { CurrentUser } from './current-user.decorator';
// 2. Ochiq endpoint
export { Public, IS_PUBLIC_KEY } from './public.decorator';
// 3. Rol tekshirish
export { Roles, ROLES_KEY } from './roles.decorator';
// 4. Auth kombinatsiyasi
export { Auth } from './auth.decorator';
// auth.controller.ts — ochiq endpointlar
import { Public, CurrentUser } from '../common/decorators';
@Controller('auth')
export class AuthController {
@Post('login')
@Public()
login(@Body() dto: LoginDto) { ... }
@Get('me')
getMe(@CurrentUser() user: User) { ... }
}
// users.controller.ts — himoyalangan endpointlar
import { Auth, CurrentUser } from '../common/decorators';
@Controller('users')
export class UsersController {
@Get()
@Auth('admin', 'moderator')
findAll() { ... }
@Delete(':id')
@Auth('admin')
remove(@Param('id') id: number) { ... }
@Get('profile')
@Auth() // faqat login bo'lgan, rol muhim emas
getProfile(@CurrentUser() user: User) { ... }
}
Xulosa
| Decorator turi | Yaratish usuli | Qo'llaniladi |
|---|---|---|
| Param decorator | createParamDecorator |
Metod parametriga: @CurrentUser() |
| Metadata decorator | SetMetadata |
Method/Class ga: @Roles(), @Public() |
| Kombinatsiya | applyDecorators |
Bir nechta decoratorni birlashtirish |
Nima uchun custom decorator?
- Takroriy
req.user,req.headerskabi kodlarni yo'q qilish @UseGuards(...) + @Roles(...)kabi kombinatsiyalarni bitta decorator ga yig'ish- Kalit nomlarini konstanta sifatida saqlash — imlo xatolaridan himoya
Keyingi dars
12-dars: ConfigModule — Environment Variables va Validation