NestJS Custom Decorators Tutorial with Practical Examples
You've used many of NestJS's built-in decorators throughout this course, @Controller(), @Get(), @Body(), @Roles(). At this point, you have enough foundation to build your own, and doing so is one of the clearest signs of NestJS fluency in a technical interview or a real codebase review.
This lesson goes beyond the simple @Public() and @Roles() examples from earlier modules, covering the full range of custom decorator types, and showing how to compose multiple decorators into one reusable, expressive unit.
NestJS Custom Decorators: Learning Objectives
- Understand the three categories of custom decorators: class, method, and parameter.
- Build a custom parameter decorator using createParamDecorator().
- Build a composed decorator using applyDecorators() to bundle multiple decorators into one.
- Pass configuration data into a custom decorator.
- Recognize when a custom decorator is the right tool versus a plain utility function.
NestJS Custom Decorators: Key Terms and Definitions
- Custom decorator: A decorator built using NestJS's own APIs (SetMetadata, createParamDecorator, applyDecorators) to encapsulate reusable, declarative logic.
- createParamDecorator(): A NestJS function used to build a custom parameter decorator that extracts data from the current ExecutionContext.
- applyDecorators(): A NestJS utility function that combines multiple existing decorators into a single new decorator.
- Decorator factory: A function that returns a decorator, allowing the decorator to accept configuration arguments, such as @Roles('admin').
- ExecutionContext: An object providing access to details of the current request, passed into guards, interceptors, and custom parameter decorators.
How NestJS Custom Decorators Works: Detailed Explanation
Custom decorators in NestJS fall into the same three categories covered when you learned how built-in decorators work: class decorators (like a hypothetical @Auditable() marking an entire controller), method decorators (like the @Roles() decorator built in Module 4), and parameter decorators (like the @CurrentUser() decorator also built in Module 4). Building your own of any of these follows the same underlying mechanism, SetMetadata() for attaching metadata, or createParamDecorator() for extracting request data into a parameter.
A parameter decorator built with createParamDecorator() accepts a callback receiving two arguments: a 'data' parameter representing whatever configuration value was passed to the decorator when it was used (such as @CurrentUser('email') passing 'email' as data), and the ExecutionContext, which the callback typically converts to the HTTP context to access the current request. This lets a single decorator implementation support both a no-argument form (returning the whole object) and an argument form (returning just one specific property), simply by checking whether 'data' was provided.
As your custom decorators multiply, particularly ones commonly used together, like an authentication guard, a specific role requirement, and an API documentation tag, repeatedly stacking three or four decorators on every protected admin route becomes noisy and easy to get wrong. NestJS's applyDecorators() function solves this by letting you compose several existing decorators into one new, named decorator. For example, an @AdminOnly() decorator might internally apply @UseGuards(JwtAuthGuard, RolesGuard), @Roles('admin'), and even a Swagger @ApiBearerAuth() tag (covered later in this module) all at once, so a controller method only needs a single, self-documenting @AdminOnly() decorator instead of four separate ones.
Understanding when a custom decorator is genuinely the right tool matters too. Decorators are ideal specifically when you need to extract or transform data at the exact point where a route handler's parameters are declared (like @CurrentUser()), or when you want a single, reusable, declarative marker attached directly to a route or controller (like @Roles() or a composed @AdminOnly()). For logic that doesn't need to hook into this declarative, parameter-level or metadata-based mechanism, a plain exported function or a service method is often simpler and doesn't need the added ceremony of a custom decorator at all.
Interview-Friendly Explanation
A strong interview or viva answer for nestjs custom decorators must go beyond a one-line definition. Interviewers evaluating experienced backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.
- Definition point: Custom decorator: A decorator built using NestJS's own APIs (SetMetadata, createParamDecorator, applyDecorators) to encapsulate reusable, declarative logic.
- Working point: Build a custom parameter decorator using createParamDecorator().
- Example point: Large NestJS codebases commonly build composed decorators like @AdminOnly() or @ApiKeyProtected() specifically to reduce the repetitive decorator stacking seen across dozens of similarly-protected routes.
- Conclusion point: Custom decorators are a natural extension of the same NestJS APIs you already used for @Public() and @Roles() in Module 4.
How to Answer This in a Technical Interview
- Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
- Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
- Mention a relevant trade-off or alternative approach, since interviewers often probe this contrast.
- Close with one benefit, limitation, or production use case.
- Avoid vague answers that only restate the term — interviewers filter these out immediately.
Practical Scenario
Imagine you are scaling a production backend for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Zomato. Every lesson in this module maps directly to a decision you will make while making that backend more advanced, performant, and maintainable at scale.
NestJS Custom Decorators: Architecture and Flow Diagram
Visualize the three custom decorator categories and how they attach:
[Class decorator] --> attaches to an entire Controller/Module class
[Method decorator] --> attaches to one route handler (e.g. @Roles('admin'))
[Parameter decorator] --> attaches to one argument within a handler's signature (e.g. @CurrentUser())
[applyDecorators(A, B, C)] --> bundles decorators A, B, and C into one new decorator, e.g. @AdminOnly()
Built Using vs Example: Comparison Table
| Decorator Type | Built Using | Example |
|---|---|---|
| Class decorator | SetMetadata() or a custom factory | @Auditable() marking an entire controller for logging |
| Method decorator | SetMetadata() | @Roles('admin') attaching required role metadata |
| Parameter decorator | createParamDecorator() | @CurrentUser() extracting request.user |
| Composed decorator | applyDecorators() | @AdminOnly() bundling guards, roles, and Swagger tags together |
NestJS Custom Decorators: NestJS Code Example
import { createParamDecorator, ExecutionContext, applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';
// A flexible parameter decorator supporting both @CurrentUser() and @CurrentUser('email')
export const CurrentUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// A composed decorator bundling authentication + role-checking into one reusable marker
export function AdminOnly() {
return applyDecorators(
UseGuards(AuthGuard('jwt'), RolesGuard),
Roles('admin'),
);
}
// Usage in a controller
import { Controller, Get } from '@nestjs/common';
@Controller('admin')
export class AdminController {
@AdminOnly() // replaces three separate decorators with one
@Get('dashboard')
getDashboard(@CurrentUser() user: any, @CurrentUser('email') email: string) {
return { message: 'Welcome, admin', user, email };
}
}
CurrentUser demonstrates a parameter decorator that adapts based on whether it's given an argument: calling @CurrentUser() returns the entire user object, while @CurrentUser('email') returns just the email property, both handled by the same single implementation checking whether 'data' was provided. AdminOnly() demonstrates composition using applyDecorators(), bundling UseGuards(AuthGuard('jwt'), RolesGuard) and Roles('admin') into one new decorator; applying just @AdminOnly() to getDashboard achieves exactly the same protection as stacking all three original decorators manually, but with a single, self-documenting, reusable marker that's far less error-prone to apply consistently across many admin routes.
Real-World NestJS Custom Decorators Industry Examples
- Large NestJS codebases commonly build composed decorators like @AdminOnly() or @ApiKeyProtected() specifically to reduce the repetitive decorator stacking seen across dozens of similarly-protected routes.
- Logging and auditing systems often build a custom @Auditable() class decorator that attaches metadata an interceptor later reads to decide which controllers require detailed request logging.
- Multi-tenant SaaS applications frequently build a custom @TenantId() parameter decorator, mirroring @CurrentUser(), to cleanly extract which organization a request belongs to.
- API teams building extensive Swagger documentation often create composed decorators bundling common response decorators (like @ApiOkResponse() and @ApiUnauthorizedResponse()) to avoid repeating the same Swagger annotations on every similar endpoint.
NestJS Custom Decorators Interview Questions and Answers
Q1. What are the three categories of custom decorators in NestJS?
Short answer: Class decorators attach metadata or behavior to an entire class (like a controller), method decorators attach to a specific route handler (like @Roles()), and parameter decorators attach to a specific argument within a method's signature (like @CurrentUser()), each built using different NestJS APIs depending on the target.
Detailed explanation: Custom decorators in NestJS fall into the same three categories covered when you learned how built-in decorators work: class decorators (like a hypothetical @Auditable() marking an entire controller), method decorators (like the @Roles() decorator built in Module 4), and parameter decorators (like the @CurrentUser() decorator also built in Module 4). Building your own of any of these follows the same underlying mechanism, SetMetadata() for attaching metadata, or createParamDecorator() for extracting request data into a parameter. A parameter decorator built with createParamDecorator() accepts a callback receiving two arguments: a 'data' parameter representing whatever configuration value was passed to the decorator when it was used (such as @CurrentUser('email') passing 'email' as data), and the ExecutionContext, which the callback typically converts to the HTTP context to access the current request. This lets a single decorator implementation support both a no-argument form (returning the whole object) and an argument form (returning just one specific property), simply by checking whether 'data' was provided. As your custom decorators multiply, particularly ones commonly used together, like an authentication guard, a specific role requirement, and an API documentation tag, repeatedly stacking three or four decorators on every protected admin route becomes noisy and easy to get wrong. NestJS's applyDecorators() function solves this by letting you compose several existing decorators into one new, named decorator. For example, an @AdminOnly() decorator might internally apply @UseGuards(JwtAuthGuard, RolesGuard), @Roles('admin'), and even a Swagger @ApiBearerAuth() tag (covered later in this module) all at once, so a controller method only needs a single, self-documenting @AdminOnly() decorator instead of four separate ones. Understanding when a custom decorator is genuinely the right tool matters too. Decorators are ideal specifically when you need to extract or transform data at the exact point where a route handler's parameters are declared (like @CurrentUser()), or when you want a single, reusable, declarative marker attached directly to a route or controller (like @Roles() or a composed @AdminOnly()). For logic that doesn't need to hook into this declarative, parameter-level or metadata-based mechanism, a plain exported function or a service method is often simpler and doesn't need the added ceremony of a custom decorator at all.
Practical example: Large NestJS codebases commonly build composed decorators like @AdminOnly() or @ApiKeyProtected() specifically to reduce the repetitive decorator stacking seen across dozens of similarly-protected routes.
Interview tip: Three decorator categories: class, method, parameter — each attaches to a different target.
Revision hook: Custom decorators are a natural extension of the same NestJS APIs you already used for @Public() and @Roles() in Module 4.
Q2. How would you build a parameter decorator that supports both @CurrentUser() and @CurrentUser('email')?
Short answer: Using createParamDecorator(), the callback receives a 'data' argument representing whatever value was passed to the decorator. By checking whether data is provided, the same implementation can return the entire user object when called with no arguments, or a specific property (like data ? user[data] : user) when a key is provided.
Detailed explanation: CurrentUser demonstrates a parameter decorator that adapts based on whether it's given an argument: calling @CurrentUser() returns the entire user object, while @CurrentUser('email') returns just the email property, both handled by the same single implementation checking whether 'data' was provided. AdminOnly() demonstrates composition using applyDecorators(), bundling UseGuards(AuthGuard('jwt'), RolesGuard) and Roles('admin') into one new decorator; applying just @AdminOnly() to getDashboard achieves exactly the same protection as stacking all three original decorators manually, but with a single, self-documenting, reusable marker that's far less error-prone to apply consistently across many admin routes.
Practical example: Logging and auditing systems often build a custom @Auditable() class decorator that attaches metadata an interceptor later reads to decide which controllers require detailed request logging.
Interview tip: createParamDecorator() builds parameter decorators; the callback receives (data, ExecutionContext).
Revision hook: A flexible parameter decorator supporting an optional argument covers many real-world use cases with one implementation.
Q3. What problem does applyDecorators() solve?
Short answer: applyDecorators() lets you bundle several existing decorators (such as guards, role requirements, and metadata) into a single new, named decorator, reducing the repetitive, error-prone practice of stacking multiple individual decorators on every route that needs the same combination of behavior.
Detailed explanation: Custom decorators in NestJS extend the same mechanisms used by built-in ones: class and method decorators attach metadata via SetMetadata(), while parameter decorators, built with createParamDecorator(), extract data from the current request directly into a handler's argument list. A flexible parameter decorator can support both a no-argument and an argument form by checking the 'data' value passed to its callback, as shown with a @CurrentUser()/@CurrentUser('email') example. When several decorators, like authentication guards, role requirements, and documentation tags, are repeatedly used together, applyDecorators() lets you bundle them into a single, composed, self-documenting decorator like @AdminOnly(), reducing repetition and the risk of forgetting one piece of a multi-decorator protection pattern.
Practical example: Multi-tenant SaaS applications frequently build a custom @TenantId() parameter decorator, mirroring @CurrentUser(), to cleanly extract which organization a request belongs to.
Interview tip: applyDecorators() composes multiple decorators into one new, reusable decorator.
Revision hook: applyDecorators() is the tool for reducing repetition once you notice the same decorator combination appearing across many routes.
Q4. When would a plain utility function be a better choice than building a custom decorator?
Short answer: When the logic doesn't need to hook into a route handler's parameter list or attach declarative metadata to a class or method, a plain exported function or service method is typically simpler and avoids the unnecessary ceremony of building and maintaining a custom decorator for logic that doesn't benefit from that mechanism.
Detailed explanation: Custom decorators in NestJS fall into the same three categories covered when you learned how built-in decorators work: class decorators (like a hypothetical @Auditable() marking an entire controller), method decorators (like the @Roles() decorator built in Module 4), and parameter decorators (like the @CurrentUser() decorator also built in Module 4). Building your own of any of these follows the same underlying mechanism, SetMetadata() for attaching metadata, or createParamDecorator() for extracting request data into a parameter. A parameter decorator built with createParamDecorator() accepts a callback receiving two arguments: a 'data' parameter representing whatever configuration value was passed to the decorator when it was used (such as @CurrentUser('email') passing 'email' as data), and the ExecutionContext, which the callback typically converts to the HTTP context to access the current request. This lets a single decorator implementation support both a no-argument form (returning the whole object) and an argument form (returning just one specific property), simply by checking whether 'data' was provided. As your custom decorators multiply, particularly ones commonly used together, like an authentication guard, a specific role requirement, and an API documentation tag, repeatedly stacking three or four decorators on every protected admin route becomes noisy and easy to get wrong. NestJS's applyDecorators() function solves this by letting you compose several existing decorators into one new, named decorator. For example, an @AdminOnly() decorator might internally apply @UseGuards(JwtAuthGuard, RolesGuard), @Roles('admin'), and even a Swagger @ApiBearerAuth() tag (covered later in this module) all at once, so a controller method only needs a single, self-documenting @AdminOnly() decorator instead of four separate ones. Understanding when a custom decorator is genuinely the right tool matters too. Decorators are ideal specifically when you need to extract or transform data at the exact point where a route handler's parameters are declared (like @CurrentUser()), or when you want a single, reusable, declarative marker attached directly to a route or controller (like @Roles() or a composed @AdminOnly()). For logic that doesn't need to hook into this declarative, parameter-level or metadata-based mechanism, a plain exported function or a service method is often simpler and doesn't need the added ceremony of a custom decorator at all.
Practical example: API teams building extensive Swagger documentation often create composed decorators bundling common response decorators (like @ApiOkResponse() and @ApiUnauthorizedResponse()) to avoid repeating the same Swagger annotations on every similar endpoint.
Interview tip: Composed decorators (like @AdminOnly()) reduce repetitive decorator stacking across similar routes.
Revision hook: Not every reusable piece of logic needs to become a decorator — reach for one only when it genuinely fits the declarative, parameter/metadata-based model.
NestJS Custom Decorators MCQs and Practice Questions
1. Which NestJS function is used to build a custom parameter decorator?
- SetMetadata()
- createParamDecorator()
- applyDecorators()
- UseGuards()
Answer: B. createParamDecorator()
Explanation: createParamDecorator() is specifically designed to build custom parameter decorators, providing access to the ExecutionContext to extract and return data as a method argument.
Concept link: Three decorator categories: class, method, parameter — each attaches to a different target.
Why this matters: Custom decorators are a natural extension of the same NestJS APIs you already used for @Public() and @Roles() in Module 4.
2. What does applyDecorators() do?
- Deletes existing decorators
- Combines multiple decorators into a single new, reusable decorator
- Converts a decorator into a guard
- Removes the need for decorators entirely
Answer: B. Combines multiple decorators into a single new, reusable decorator
Explanation: applyDecorators() bundles several decorators together into one composed decorator, reducing repetition when the same combination is needed across many routes.
Concept link: createParamDecorator() builds parameter decorators; the callback receives (data, ExecutionContext).
Why this matters: A flexible parameter decorator supporting an optional argument covers many real-world use cases with one implementation.
3. In a custom parameter decorator, what does the 'data' argument represent?
- The current HTTP method
- Whatever value was passed as an argument to the decorator when it was used
- The database connection
- The response status code
Answer: B. Whatever value was passed as an argument to the decorator when it was used
Explanation: The 'data' argument in createParamDecorator()'s callback corresponds to any value passed when the decorator is applied, such as the string 'email' in @CurrentUser('email').
Concept link: applyDecorators() composes multiple decorators into one new, reusable decorator.
Why this matters: applyDecorators() is the tool for reducing repetition once you notice the same decorator combination appearing across many routes.
4. Which category of decorator is @Roles('admin') an example of?
- Class decorator
- Method decorator
- Parameter decorator
- Composed decorator
Answer: B. Method decorator
Explanation: @Roles('admin') is applied directly to a specific route handler method, attaching metadata describing that route's role requirement, making it a method decorator.
Concept link: Composed decorators (like @AdminOnly()) reduce repetitive decorator stacking across similar routes.
Why this matters: Not every reusable piece of logic needs to become a decorator — reach for one only when it genuinely fits the declarative, parameter/metadata-based model.
Common NestJS Custom Decorators Mistakes to Avoid
- Building an overly complex custom decorator for logic that would be simpler and clearer as a plain utility function or service method.
- Forgetting to handle the no-argument case in a flexible parameter decorator, causing it to break when used without any configuration value.
- Duplicating the same stack of three or four decorators across dozens of routes instead of composing them once with applyDecorators().
- Confusing which NestJS API to use for which decorator type, such as trying to use SetMetadata() to build a parameter decorator instead of createParamDecorator().
NestJS Custom Decorators: Interview Notes and Exam Tips
- Three decorator categories: class, method, parameter — each attaches to a different target.
- createParamDecorator() builds parameter decorators; the callback receives (data, ExecutionContext).
- applyDecorators() composes multiple decorators into one new, reusable decorator.
- Composed decorators (like @AdminOnly()) reduce repetitive decorator stacking across similar routes.
Key NestJS Custom Decorators Takeaways
- Custom decorators are a natural extension of the same NestJS APIs you already used for @Public() and @Roles() in Module 4.
- A flexible parameter decorator supporting an optional argument covers many real-world use cases with one implementation.
- applyDecorators() is the tool for reducing repetition once you notice the same decorator combination appearing across many routes.
- Not every reusable piece of logic needs to become a decorator — reach for one only when it genuinely fits the declarative, parameter/metadata-based model.
NestJS Custom Decorators: Summary
Custom decorators in NestJS extend the same mechanisms used by built-in ones: class and method decorators attach metadata via SetMetadata(), while parameter decorators, built with createParamDecorator(), extract data from the current request directly into a handler's argument list. A flexible parameter decorator can support both a no-argument and an argument form by checking the 'data' value passed to its callback, as shown with a @CurrentUser()/@CurrentUser('email') example. When several decorators, like authentication guards, role requirements, and documentation tags, are repeatedly used together, applyDecorators() lets you bundle them into a single, composed, self-documenting decorator like @AdminOnly(), reducing repetition and the risk of forgetting one piece of a multi-decorator protection pattern.