Custom Decorators

~15 daq · NestJS · Boshlang'ich daraja

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.headers kabi 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