Architekcie imperium! Senator Cicero zauważył, że wiele operacji w kontrolerach powtarza się - wyciąganie danych użytkownika z requestu, sprawdzanie ról, dodawanie metadanych. Czas nauczyć się Custom Decorators - sztuki tworzenia własnych pieczęci i insygniów, które automatyzują powtarzalne wzorce w kodzie NestJS!
W imperium rzymskim każdy urzędnik nosił insygnia swojej rangi - pierścień senatora, toga pretora, laska konsula. Te insygnia automatycznie informowały innych o uprawnieniach i roli danej osoby. Custom Decorators w NestJS działają dokładnie tak samo - to specjalne oznaczenia, które dodajesz do metod, klas lub parametrów, by automatycznie wykonywały określone operacje.
Najczesciej tworzonym typem dekoratora sa parameter decorators - wyciagaja dane z requestu i wstrzykuja je bezposrednio do parametrow metody kontrolera. NestJS dostarcza funkcje
createParamDecorator do tego celu.1// decorators/current-user.decorator.ts
2import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3
4// Dekorator @CurrentUser() - wyciaga dane uzytkownika z requestu
5export const CurrentUser = createParamDecorator(
6 (data: string, ctx: ExecutionContext) => {
7 const request = ctx.switchToHttp().getRequest();
8 const user = request.user; // Ustawiony wczesniej przez AuthGuard
9
10 // Jesli podano konkretne pole, zwroc tylko je
11 return data ? user?.[data] : user;
12 },
13);
14
15// Uzycie w kontrolerze
16@Controller('legiones')
17export class LegionController {
18 @Get('profile')
19 @UseGuards(JwtAuthGuard)
20 getProfile(@CurrentUser() user: any) {
21 // user = { id: 1, name: 'Marcus', rank: 'Centurio', legion: 'Legio X' }
22 return { message: `Ave, ${user.name}!`, profile: user };
23 }
24
25 @Get('rank')
26 @UseGuards(JwtAuthGuard)
27 getRank(@CurrentUser('rank') rank: string) {
28 // rank = 'Centurio' (tylko konkretne pole)
29 return { rank };
30 }
31}Zauważ, ze argument
data w createParamDecorator to wartosc przekazana do dekoratora - @CurrentUser('rank') oznacza, ze data === 'rank'.Kolejnym kluczowym narzedziem sa metadane.
SetMetadata pozwala przypisac dodatkowe informacje do metody lub klasy, a Reflector pozwala je pozniej odczytac - na przyklad w guardzie.1// decorators/roles.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3
4// Prosty dekorator @Roles() ustawiajacy metadane
5export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
6
7// Uzycie
8@Controller('tributa')
9export class TributeController {
10 @Post()
11 @Roles('consul', 'praetor') // Tylko consul i praetor moga tworzyc
12 createTribute(@Body() dto: any) {
13 return { message: 'Tribut utworzony!' };
14 }
15
16 @Delete(':id')
17 @Roles('consul') // Tylko consul moze usuwac
18 removeTribute(@Param('id') id: string) {
19 return { message: `Tribut ${id} usuniety` };
20 }
21}Metadane same w sobie nic nie robia - potrzebujemy guarda, ktory je odczyta za pomoca
Reflector:1// guards/roles.guard.ts
2import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
3import { Reflector } from '@nestjs/core';
4
5@Injectable()
6export class RolesGuard implements CanActivate {
7 constructor(private reflector: Reflector) {}
8
9 canActivate(context: ExecutionContext): boolean {
10 // Odczytaj metadane 'roles' z handlera
11 const requiredRoles = this.reflector.get<string[]>(
12 'roles',
13 context.getHandler(),
14 );
15
16 // Brak wymaganych rol = dostep dla wszystkich
17 if (!requiredRoles) return true;
18
19 const request = context.switchToHttp().getRequest();
20 const user = request.user;
21
22 // Sprawdz czy uzytkownik ma przynajmniej jedna wymagana role
23 return requiredRoles.some(role => user?.roles?.includes(role));
24 }
25}Mozesz tworzyc wlasne dekoratory metod, ktore laczą kilka dekoratorow w jeden - to jak pieczec na dokumencie, ktora jednoczesnie potwierdza autentycznosc, rang i uprawnienia.
1// decorators/public.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3
4// Dekorator oznaczajacy endpoint jako publiczny (bez autoryzacji)
5export const IS_PUBLIC_KEY = 'isPublic';
6export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
7
8// Uzycie
9@Controller('forum')
10export class ForumController {
11 @Get()
12 @Public() // Ten endpoint jest publiczny
13 getPublicPosts() {
14 return { posts: ['Wiadomosc z Forum Romanum'] };
15 }
16}Funkcja
applyDecorators pozwala polaczyc wiele dekoratorow w jeden - to jak pieczec glowna, ktora zastepuje kilka mniejszych pieczeci.1// decorators/auth.decorator.ts
2import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common';
3import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';
4import { JwtAuthGuard } from '../guards/jwt-auth.guard';
5import { RolesGuard } from '../guards/roles.guard';
6
7// Zlozony dekorator @Auth() laczacy guard, role i dokumentacje
8export function Auth(...roles: string[]) {
9 return applyDecorators(
10 SetMetadata('roles', roles),
11 UseGuards(JwtAuthGuard, RolesGuard),
12 ApiBearerAuth(),
13 ApiUnauthorizedResponse({ description: 'Brak autoryzacji' }),
14 );
15}
16
17// Uzycie - jeden dekorator zamiast czterech!
18@Controller('imperium')
19export class ImperiumController {
20 @Post('decree')
21 @Auth('consul', 'praetor') // Autoryzacja + role + Swagger
22 issueDecree(@Body() decree: any) {
23 return { message: 'Dekret wydany!', decree };
24 }
25
26 @Get('treasury')
27 @Auth('consul', 'quaestor') // Tylko consul i quaestor
28 getTreasury() {
29 return { treasury: 'Stan skarbca imperium' };
30 }
31}Dzieki
applyDecorators Twoj kod jest czystszy i latwiejszy w utrzymaniu. Zamiast powtarzac te same cztery dekoratory na kazdym endpoincie, uzywasz jednego @Auth().1// decorators/log-action.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3
4// Dekorator do logowania akcji w kronikach imperium
5export const LOG_ACTION_KEY = 'logAction';
6export const LogAction = (actionName: string) =>
7 SetMetadata(LOG_ACTION_KEY, actionName);
8
9// decorators/rate-limit.decorator.ts
10export const RATE_LIMIT_KEY = 'rateLimit';
11export const RateLimit = (maxRequests: number, windowMs: number) =>
12 SetMetadata(RATE_LIMIT_KEY, { maxRequests, windowMs });
13
14// Uzycie w kontrolerze
15@Controller('legiones')
16export class LegionController {
17 @Post('recruit')
18 @Auth('consul')
19 @LogAction('RECRUIT_LEGIONARY')
20 @RateLimit(10, 60000) // Max 10 rekrutacji na minute
21 recruitLegionary(@Body() data: any, @CurrentUser() user: any) {
22 return {
23 message: `${user.name} zrekrutowal nowego legioniste`,
24 legionary: data,
25 };
26 }
27}Custom Decorators to potezne narzedzie NestJS - pozwalaja tworzyc czytelny, wielokrotnie uzywalny kod. Kazdy dekorator to jak pieczec senatora - niesie ze soba konkretne znaczenie i automatycznie wykonuje okreslone operacje!