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.