NestJS Decorators Explained (@Module, @Controller, @Injectable)
By now, you have used decorators like @Module(), @Controller(), @Injectable(), @Get(), @Param(), and @Body() throughout this course without necessarily stopping to ask what they actually are or how they work internally. This lesson closes that gap.
Understanding decorators deeply is what allows you to move from copying NestJS patterns to genuinely understanding why they work, and eventually, to writing your own custom decorators when the built-in ones do not quite fit your needs.
NestJS Decorators Explained: Learning Objectives
- Explain what a decorator is at the TypeScript language level.
- Understand the specific role of @Module(), @Controller(), and @Injectable() in NestJS's architecture.
- Differentiate between class decorators, method decorators, and parameter decorators.
- Understand how NestJS reads decorator metadata to build its dependency injection graph.
- Create a simple custom parameter decorator.
NestJS Decorators Explained: Key Terms and Definitions
- Decorator: A special declaration in TypeScript, prefixed with @, that can be attached to a class, method, property, or parameter to add metadata or modify behavior.
- Class decorator: A decorator applied directly above a class declaration, such as @Module() or @Controller(), which attaches metadata describing the entire class.
- Method decorator: A decorator applied above a class method, such as @Get() or @Post(), which attaches metadata describing how that specific method should be treated.
- Parameter decorator: A decorator applied to a specific parameter within a method, such as @Param() or @Body(), which tells NestJS how to extract and inject a value into that parameter.
- Reflect metadata: A underlying mechanism (via the reflect-metadata library) that NestJS uses to store and later retrieve the metadata attached by decorators.
- Custom decorator: A decorator you define yourself, often to encapsulate repeated logic or extract custom data from a request in a reusable way.
How NestJS Decorators Explained Works: Detailed Explanation
At the TypeScript level, a decorator is simply a function that gets called with specific arguments depending on what it decorates, and that function has the opportunity to record metadata about, or even modify, the thing it decorates. NestJS builds essentially its entire architecture on top of this single language feature, combined with the reflect-metadata library, which provides a way to attach and later retrieve arbitrary metadata on classes and their members at runtime.
@Module() is a class decorator. When you write @Module({ controllers: [...], providers: [...], imports: [...] }) above a class, NestJS stores this configuration object as metadata attached to that class. Later, when NestFactory.create() processes your root module, it reads this metadata to understand which controllers and providers belong to that module and how modules relate to each other.
@Controller() is also a class decorator, but with a narrower purpose: it marks a class as a request handler for a particular base route and stores that base path as metadata. @Injectable() is likewise a class decorator, marking a class as available for NestJS's dependency injection system, essentially flagging 'this class can be instantiated and injected elsewhere by the IoC container.'
Method decorators like @Get(), @Post(), @Put(), and @Delete() work at a more granular level, attaching metadata to individual methods within a controller class, recording both the HTTP verb and the specific path segment that method should respond to. When a request comes in, NestJS's router reads this metadata across all registered controllers to determine which method should handle it.
Parameter decorators like @Param(), @Query(), and @Body() go even further, attaching metadata to individual arguments within a method's parameter list. This metadata tells NestJS exactly which part of the incoming request (route params, query string, or body) should be extracted and passed as that specific argument when the method executes.
Understanding this layered system, class-level, method-level, and parameter-level decorators, all working together through shared metadata, demystifies what once looked like framework magic. It also opens the door to writing your own custom decorators, for example a @CurrentUser() decorator that extracts the currently authenticated user from a request in a reusable, readable way across many controllers.
Interview-Friendly Explanation
A strong interview or viva answer for nestjs decorators explained must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught 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: Decorator: A special declaration in TypeScript, prefixed with @, that can be attached to a class, method, property, or parameter to add metadata or modify behavior.
- Working point: Understand the specific role of @Module(), @Controller(), and @Injectable() in NestJS's architecture.
- Example point: Authentication systems in production NestJS apps almost universally implement a custom @CurrentUser() or @AuthUser() decorator, exactly like the example shown, to cleanly access the logged-in user in any controller.
- Conclusion point: Every NestJS decorator you have used so far follows the same underlying pattern: attach metadata now, read it later.
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 how it compares to plain Express.js if relevant, since interviewers often probe this contrast.
- Close with one benefit, trade-off, or production use case.
- Avoid vague answers like "it just works" — interviewers filter these out immediately.
Practical Scenario
Imagine you are building a production backend for a SaaS product, similar to systems used at companies like Swiggy, Razorpay, or Freshworks. Every lesson in this NestJS course maps directly to a decision you will make while building that backend: how you structure code, how you separate concerns, how you test it, and how you scale it under real traffic.
NestJS Decorators Explained: Architecture and Flow Diagram
Visualize three nested boxes representing decorator levels:
[Class-level: @Module(), @Controller(), @Injectable() — attach metadata to the whole class]
└── [Method-level: @Get(), @Post(), @Put(), @Delete() — attach metadata to a specific method]
└── [Parameter-level: @Param(), @Query(), @Body() — attach metadata to a specific argument]
Add a note below: 'NestJS reads all of this metadata at startup and at request-time to build the application and route incoming requests correctly.'
NestJS Decorators Explained: Decorator Type Reference Table
| Decorator Type | Examples | What It Attaches Metadata To |
|---|---|---|
| Class decorator | @Module(), @Controller(), @Injectable() | An entire class |
| Method decorator | @Get(), @Post(), @Put(), @Delete() | A specific method within a class |
| Parameter decorator | @Param(), @Query(), @Body() | A specific argument within a method's parameter list |
NestJS Decorators Explained: NestJS Code Example
// A simplified custom parameter decorator, similar in spirit to NestJS's built-in ones
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
// Assumes some earlier middleware/guard has attached 'user' to the request
return request.user;
},
);
// Using the custom decorator inside a controller
import { Controller, Get } from '@nestjs/common';
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@CurrentUser() user: any) {
return { message: `Hello, ${user?.name || 'guest'}!` };
}
}
This example creates a custom parameter decorator called @CurrentUser() using NestJS's createParamDecorator helper. Internally, it accesses the underlying HTTP request through the ExecutionContext and pulls out a user object that some earlier authentication step is assumed to have attached. Once defined, @CurrentUser() can be used inside any controller method exactly like a built-in decorator such as @Body() or @Param(), demonstrating that NestJS's own decorators are not fundamentally different from ones you can build yourself, they simply ship as part of the framework.
Real-World NestJS Decorators Explained Industry Examples
- Authentication systems in production NestJS apps almost universally implement a custom @CurrentUser() or @AuthUser() decorator, exactly like the example shown, to cleanly access the logged-in user in any controller.
- Multi-tenant SaaS platforms often build a custom @TenantId() decorator to extract which company or organization a request belongs to, keeping that logic out of every individual controller method.
- Logging and auditing systems sometimes use custom method decorators to automatically wrap specific route handlers with additional logging behavior without modifying the handler's own code.
- API versioning strategies in some NestJS applications use custom decorators to mark and route requests differently based on a version header extracted from incoming requests.
NestJS Decorators Explained Interview Questions and Answers
Q1. What is a decorator in TypeScript, and how does NestJS use this feature?
Short answer: A decorator is a function, prefixed with @, that can be attached to a class, method, property, or parameter to add metadata or alter behavior. NestJS uses decorators extensively, alongside the reflect-metadata library, to attach configuration and routing information directly onto classes and methods, which its dependency injection container and router then read at runtime.
Detailed explanation: At the TypeScript level, a decorator is simply a function that gets called with specific arguments depending on what it decorates, and that function has the opportunity to record metadata about, or even modify, the thing it decorates. NestJS builds essentially its entire architecture on top of this single language feature, combined with the reflect-metadata library, which provides a way to attach and later retrieve arbitrary metadata on classes and their members at runtime. @Module() is a class decorator. When you write @Module({ controllers: [...], providers: [...], imports: [...] }) above a class, NestJS stores this configuration object as metadata attached to that class. Later, when NestFactory.create() processes your root module, it reads this metadata to understand which controllers and providers belong to that module and how modules relate to each other. @Controller() is also a class decorator, but with a narrower purpose: it marks a class as a request handler for a particular base route and stores that base path as metadata. @Injectable() is likewise a class decorator, marking a class as available for NestJS's dependency injection system, essentially flagging 'this class can be instantiated and injected elsewhere by the IoC container.' Method decorators like @Get(), @Post(), @Put(), and @Delete() work at a more granular level, attaching metadata to individual methods within a controller class, recording both the HTTP verb and the specific path segment that method should respond to. When a request comes in, NestJS's router reads this metadata across all registered controllers to determine which method should handle it. Parameter decorators like @Param(), @Query(), and @Body() go even further, attaching metadata to individual arguments within a method's parameter list. This metadata tells NestJS exactly which part of the incoming request (route params, query string, or body) should be extracted and passed as that specific argument when the method executes. Understanding this layered system, class-level, method-level, and parameter-level decorators, all working together through shared metadata, demystifies what once looked like framework magic. It also opens the door to writing your own custom decorators, for example a @CurrentUser() decorator that extracts the currently authenticated user from a request in a reusable, readable way across many controllers.
Practical example: Authentication systems in production NestJS apps almost universally implement a custom @CurrentUser() or @AuthUser() decorator, exactly like the example shown, to cleanly access the logged-in user in any controller.
Interview tip: Three decorator levels to remember: class-level (@Module, @Controller, @Injectable), method-level (@Get, @Post), parameter-level (@Param, @Query, @Body).
Revision hook: Every NestJS decorator you have used so far follows the same underlying pattern: attach metadata now, read it later.
Q2. What is the difference between @Module(), @Get(), and @Param() in terms of decorator type?
Short answer: @Module() is a class decorator, attaching metadata to an entire class describing its controllers, providers, and imports. @Get() is a method decorator, attaching metadata to a specific method describing which HTTP verb and path it should handle. @Param() is a parameter decorator, attaching metadata to a specific method argument describing which part of the incoming request should be extracted into it.
Detailed explanation: This example creates a custom parameter decorator called @CurrentUser() using NestJS's createParamDecorator helper. Internally, it accesses the underlying HTTP request through the ExecutionContext and pulls out a user object that some earlier authentication step is assumed to have attached. Once defined, @CurrentUser() can be used inside any controller method exactly like a built-in decorator such as @Body() or @Param(), demonstrating that NestJS's own decorators are not fundamentally different from ones you can build yourself, they simply ship as part of the framework.
Practical example: Multi-tenant SaaS platforms often build a custom @TenantId() decorator to extract which company or organization a request belongs to, keeping that logic out of every individual controller method.
Interview tip: Decorators attach metadata; NestJS's framework code reads that metadata later to build the app and route requests.
Revision hook: Understanding the three decorator levels (class, method, parameter) makes NestJS's entire declarative style far less mysterious.
Q3. How would you create a custom decorator in NestJS to extract the logged-in user from a request?
Short answer: You would use NestJS's createParamDecorator helper function, passing it a callback that receives an ExecutionContext, switches to the HTTP context to access the underlying request object, and returns whatever property represents the authenticated user, typically one attached earlier by an authentication guard or middleware.
Detailed explanation: Decorators are the foundation of NestJS's entire declarative architecture. Class decorators like @Module(), @Controller(), and @Injectable() attach configuration metadata to whole classes, describing how they fit into the application. Method decorators like @Get() and @Post() attach routing metadata to individual methods, while parameter decorators like @Param(), @Query(), and @Body() attach metadata describing how to extract specific pieces of data from an incoming request. All of this metadata is stored and retrieved using the reflect-metadata library, which NestJS relies on internally. Understanding this layered decorator system not only demystifies how NestJS works but also enables you to build your own custom decorators, such as a reusable @CurrentUser() decorator, to keep your controllers clean and expressive.
Practical example: Logging and auditing systems sometimes use custom method decorators to automatically wrap specific route handlers with additional logging behavior without modifying the handler's own code.
Interview tip: createParamDecorator() is the official way to build custom parameter decorators in NestJS.
Revision hook: Custom decorators are a powerful tool for removing repeated logic from your controllers, especially around authentication and request context.
Q4. Why does NestJS rely on the reflect-metadata library?
Short answer: reflect-metadata provides the underlying mechanism for storing and retrieving metadata attached to classes and their members via decorators. Without it, TypeScript's decorator syntax alone would not be enough for NestJS to later read back the configuration information, such as which controllers belong to a module, at application startup.
Detailed explanation: At the TypeScript level, a decorator is simply a function that gets called with specific arguments depending on what it decorates, and that function has the opportunity to record metadata about, or even modify, the thing it decorates. NestJS builds essentially its entire architecture on top of this single language feature, combined with the reflect-metadata library, which provides a way to attach and later retrieve arbitrary metadata on classes and their members at runtime. @Module() is a class decorator. When you write @Module({ controllers: [...], providers: [...], imports: [...] }) above a class, NestJS stores this configuration object as metadata attached to that class. Later, when NestFactory.create() processes your root module, it reads this metadata to understand which controllers and providers belong to that module and how modules relate to each other. @Controller() is also a class decorator, but with a narrower purpose: it marks a class as a request handler for a particular base route and stores that base path as metadata. @Injectable() is likewise a class decorator, marking a class as available for NestJS's dependency injection system, essentially flagging 'this class can be instantiated and injected elsewhere by the IoC container.' Method decorators like @Get(), @Post(), @Put(), and @Delete() work at a more granular level, attaching metadata to individual methods within a controller class, recording both the HTTP verb and the specific path segment that method should respond to. When a request comes in, NestJS's router reads this metadata across all registered controllers to determine which method should handle it. Parameter decorators like @Param(), @Query(), and @Body() go even further, attaching metadata to individual arguments within a method's parameter list. This metadata tells NestJS exactly which part of the incoming request (route params, query string, or body) should be extracted and passed as that specific argument when the method executes. Understanding this layered system, class-level, method-level, and parameter-level decorators, all working together through shared metadata, demystifies what once looked like framework magic. It also opens the door to writing your own custom decorators, for example a @CurrentUser() decorator that extracts the currently authenticated user from a request in a reusable, readable way across many controllers.
Practical example: API versioning strategies in some NestJS applications use custom decorators to mark and route requests differently based on a version header extracted from incoming requests.
Interview tip: reflect-metadata is the underlying library making decorator metadata storage and retrieval possible.
Revision hook: NestJS's own built-in decorators are not magic — they are built using the exact same TypeScript and NestJS APIs available to you.
NestJS Decorators Explained MCQs and Practice Questions
1. Which type of decorator is @Injectable() classified as?
- Method decorator
- Parameter decorator
- Class decorator
- Property decorator
Answer: C. Class decorator
Explanation: @Injectable() is applied directly above a class declaration, marking the entire class as available for NestJS's dependency injection system, making it a class decorator.
Concept link: Three decorator levels to remember: class-level (@Module, @Controller, @Injectable), method-level (@Get, @Post), parameter-level (@Param, @Query, @Body).
Why this matters: Every NestJS decorator you have used so far follows the same underlying pattern: attach metadata now, read it later.
2. What kind of decorator is @Body() an example of?
- Class decorator
- Method decorator
- Parameter decorator
- Module decorator
Answer: C. Parameter decorator
Explanation: @Body() is applied to a specific argument within a method's parameter list, telling NestJS to extract the request body and pass it as that argument, making it a parameter decorator.
Concept link: Decorators attach metadata; NestJS's framework code reads that metadata later to build the app and route requests.
Why this matters: Understanding the three decorator levels (class, method, parameter) makes NestJS's entire declarative style far less mysterious.
3. Which NestJS helper function is used to create a custom parameter decorator?
- createModule()
- createParamDecorator()
- createController()
- createProvider()
Answer: B. createParamDecorator()
Explanation: createParamDecorator() is the official NestJS utility used to build custom parameter decorators that can access the ExecutionContext and extract custom data from incoming requests.
Concept link: createParamDecorator() is the official way to build custom parameter decorators in NestJS.
Why this matters: Custom decorators are a powerful tool for removing repeated logic from your controllers, especially around authentication and request context.
4. What underlying library does NestJS rely on to store and retrieve decorator metadata?
- lodash
- reflect-metadata
- rxjs
- class-validator
Answer: B. reflect-metadata
Explanation: reflect-metadata provides the mechanism NestJS uses internally to attach and later read metadata added by decorators like @Module(), @Controller(), and @Injectable().
Concept link: reflect-metadata is the underlying library making decorator metadata storage and retrieval possible.
Why this matters: NestJS's own built-in decorators are not magic — they are built using the exact same TypeScript and NestJS APIs available to you.
Common NestJS Decorators Explained Mistakes to Avoid
- Assuming decorators execute business logic directly when the class is used, when in reality most NestJS decorators simply attach metadata read later by the framework.
- Applying a class decorator like @Injectable() to a method or parameter where it does not belong, since decorators are typed to specific targets.
- Overcomplicating custom decorators for simple cases where a plain utility function would be clearer and easier for teammates to understand.
- Forgetting that reflect-metadata (or equivalent TypeScript compiler settings) must be properly configured for decorators to work correctly, though the Nest CLI sets this up automatically in generated projects.
NestJS Decorators Explained: Interview Notes and Exam Tips
- Three decorator levels to remember: class-level (@Module, @Controller, @Injectable), method-level (@Get, @Post), parameter-level (@Param, @Query, @Body).
- Decorators attach metadata; NestJS's framework code reads that metadata later to build the app and route requests.
- createParamDecorator() is the official way to build custom parameter decorators in NestJS.
- reflect-metadata is the underlying library making decorator metadata storage and retrieval possible.
Key NestJS Decorators Explained Takeaways
- Every NestJS decorator you have used so far follows the same underlying pattern: attach metadata now, read it later.
- Understanding the three decorator levels (class, method, parameter) makes NestJS's entire declarative style far less mysterious.
- Custom decorators are a powerful tool for removing repeated logic from your controllers, especially around authentication and request context.
- NestJS's own built-in decorators are not magic — they are built using the exact same TypeScript and NestJS APIs available to you.
NestJS Decorators Explained: Summary
Decorators are the foundation of NestJS's entire declarative architecture. Class decorators like @Module(), @Controller(), and @Injectable() attach configuration metadata to whole classes, describing how they fit into the application. Method decorators like @Get() and @Post() attach routing metadata to individual methods, while parameter decorators like @Param(), @Query(), and @Body() attach metadata describing how to extract specific pieces of data from an incoming request. All of this metadata is stored and retrieved using the reflect-metadata library, which NestJS relies on internally. Understanding this layered decorator system not only demystifies how NestJS works but also enables you to build your own custom decorators, such as a reusable @CurrentUser() decorator, to keep your controllers clean and expressive.