Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Custom decorators

The @Body() and @Param() decorators take data from the request, and @Roles() from the previous lesson leaves a mark on a method. You can write such helpers yourself. Two are needed most often: a parameter decorator that gives you the current user, and a decorator that combines several others into one.

A parameter decorator

Right now, to find out the user, a method would have to take the whole request object and read request.user. The createParamDecorator() function makes this shorter:

// src/auth/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
 
export const CurrentUser = createParamDecorator(
  (field: string | undefined, context: ExecutionContext) => {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return field ? user?.[field] : user;
  },
);

The function receives the decorator argument (field) and the request context and returns what goes into the method parameter. Without a field the whole user is returned.

Usage

Add a profile route to AuthController:

// src/auth/auth.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { CurrentUser } from './current-user.decorator';
 
@Controller('auth')
export class AuthController {
  // constructor and login() from the previous lesson
 
  @Get('profile')
  @UseGuards(AuthGuard)
  profile(@CurrentUser() user: { sub: number; email: string; role: string }) {
    return user;
  }
}

A GET /auth/profile request with a token returns the token contents: sub, email, role, plus the technical fields iat and exp — the issue and expiry times.

With an argument the decorator returns one field. That is how a post learns its author's id:

// src/posts/posts.controller.ts
@Post()
@UseGuards(AuthGuard)
create(@Body() dto: CreatePostDto, @CurrentUser('sub') authorId: number) {
  return this.postsService.create(dto, authorId);
}

For this, the Post entity gets a @ManyToOne(() => User) relation with an author property — the same way a comment is linked to a post.

Combining decorators

Every admin-only method repeats the same lines: @Roles('admin'), @UseGuards(AuthGuard, RolesGuard). The applyDecorators() function bundles them into one decorator:

// src/auth/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
 
export function Auth(...roles: string[]) {
  return applyDecorators(Roles(...roles), UseGuards(AuthGuard, RolesGuard));
}
// src/posts/posts.controller.ts
import { Auth } from '../auth/auth.decorator';
 
@Delete(':id')
@HttpCode(204)
@Auth('admin')
remove(@Param('id', ParseIntPipe) id: number) {
  return this.postsService.remove(id);
}

@Auth() without arguments lets in any logged-in user, @Auth('admin') only an administrator. The guard order is now defined in one place and cannot be got wrong.

Common mistake

The @CurrentUser() decorator does not check login — it only reads request.user. If the method has no AuthGuard, no user appears in the request, and the parameter gets undefined. Use @CurrentUser() only together with the login guard or the @Auth() decorator.

Thanks for reading!

Donate to the site — we work hard for you.