Once a user is authenticated, you often need to restrict actions based on their role, admin versus regular user, for example. This lesson builds a role-based access control (RBAC) system using metadata and a guard.
Declaring Required Roles With a Custom Decorator
A @Roles() decorator attaches metadata to a route handler describing which roles are allowed to access it. A RolesGuard then reads this metadata using Reflector and compares it against the authenticated user's role.
SetMetadata() is the low-level building block behind most custom decorators in NestJS, it attaches arbitrary metadata to a class or method that a guard, interceptor, or pipe can later read.
getAllAndOverride() checks method-level metadata first, falling back to class-level metadata.
If no roles are declared for a route, the guard allows the request through by default.
The guard assumes request.user was already populated by an earlier auth guard.
Combine both guards with @UseGuards(AuthGuard, RolesGuard) in that order.
RBAC Cheatsheet
The pieces that make up a role-based access system.
Piece
Role
@Roles('admin')
Declares which roles may access a route
SetMetadata()
Attaches metadata used by @Roles()
Reflector
Reads metadata attached to a handler or class
RolesGuard
Compares required roles to the user's actual roles
Applying Roles to Routes
With the decorator and guard in place, protecting a route by role becomes a one-line addition, without touching the guard's implementation for every new role combination.
Simple role checks (admin, user) work well for small applications, but larger systems often need finer-grained permissions (cats:delete, cats:write) assigned to roles, letting you change what a role can do without redeploying code.
Common Mistakes
Checking roles with strict equality instead of .includes()/.some() against an array of user roles.
Forgetting to apply AuthGuard before RolesGuard, leaving request.user undefined.
Hardcoding role strings across many files instead of centralizing them as constants or an enum.
Not defaulting to "allow" when a route has no @Roles() metadata, accidentally locking out unrelated routes.
Key Takeaways
@Roles() and SetMetadata() attach role requirements directly to route handlers.
Reflector lets a guard read that metadata at runtime.
RolesGuard compares required roles against the authenticated user's roles.
Larger systems often evolve role checks into more fine-grained permission systems.
Pro Tip
Define role names as a shared enum or constant object (e.g. Role.Admin) instead of raw strings scattered across @Roles() calls, a typo in a hardcoded role string fails silently and is one of the sneakiest authorization bugs to track down.
You now know how to implement role-based access control. Next, learn about Execution Context, the object guards, interceptors, and pipes all rely on.