0
0
NestJSframework~15 mins

Reflector and custom decorators in NestJS - Deep Dive

Choose your learning style9 modes available
Overview - Reflector and custom decorators
What is it?
In NestJS, Reflector is a helper class that lets you read metadata attached to classes, methods, or parameters. Custom decorators are special functions you create to add this metadata in a clear and reusable way. Together, they help you add extra information to your code that can be checked later, like permissions or roles. This makes your code cleaner and easier to manage.
Why it matters
Without Reflector and custom decorators, you would have to write repetitive and messy code to check things like user roles or settings everywhere. This would make your app harder to maintain and more error-prone. Using these tools lets you centralize logic and keep your code neat, which saves time and reduces bugs in real projects.
Where it fits
Before learning this, you should understand basic TypeScript decorators and how NestJS uses metadata. After this, you can explore advanced NestJS features like guards, interceptors, and middleware that often use Reflector and custom decorators to control app behavior.
Mental Model
Core Idea
Reflector reads hidden notes (metadata) attached by custom decorators to parts of your code so you can make decisions based on those notes later.
Think of it like...
Imagine you put sticky notes on different pages of a book to mark important parts. Reflector is like a tool that finds those sticky notes so you know what to focus on without reading the whole book again.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Custom       │       │ Metadata      │       │ Reflector     │
│ Decorator   ├──────▶│ attached to   ├──────▶│ reads metadata│
│ (adds notes) │       │ classes/methods│       │ to guide logic│
└───────────────┘       └───────────────┘       └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding TypeScript decorators
🤔
Concept: Learn what decorators are and how they add extra info to classes or methods.
Decorators are special functions that you place above classes or methods using @ symbol. They let you add extra data or change behavior without changing the original code. For example, @Controller marks a class as a controller in NestJS.
Result
You can mark parts of your code with extra info that NestJS or your code can use later.
Understanding decorators is key because custom decorators build on this to add meaningful metadata.
2
FoundationWhat is metadata in NestJS?
🤔
Concept: Metadata is hidden information attached to code elements that can be read later.
When you use decorators, they often add metadata behind the scenes. This metadata is like hidden tags or notes attached to classes or methods. NestJS uses this metadata to know how to handle requests or apply rules.
Result
You realize that decorators don't just run code but also store useful info for later use.
Knowing metadata exists helps you see why Reflector is needed to read this hidden info.
3
IntermediateCreating custom decorators to add metadata
🤔Before reading on: do you think custom decorators can only mark classes, or can they also mark methods and parameters? Commit to your answer.
Concept: Custom decorators let you add your own metadata to classes, methods, or parameters.
You create a function that uses NestJS's SetMetadata helper to attach a key-value pair as metadata. For example, @Roles('admin') adds a 'roles' key with value ['admin'] to a method. This metadata can later be read to check permissions.
Result
You can mark your code with custom tags like roles or permissions that your app can check.
Knowing how to create custom decorators empowers you to add meaningful, reusable metadata for your app's logic.
4
IntermediateUsing Reflector to read metadata
🤔Before reading on: do you think Reflector reads metadata only from methods, or also from classes and parameters? Commit to your answer.
Concept: Reflector is a NestJS helper that reads metadata from classes, methods, or parameters at runtime.
You inject Reflector into guards or interceptors and call methods like get() or getAllAndOverride() to retrieve metadata by key. For example, you can get the 'roles' metadata from a method to decide if a user can access it.
Result
You can dynamically check metadata to control app behavior like access control.
Understanding Reflector's role clarifies how metadata added by decorators becomes actionable in your app.
5
IntermediateCombining custom decorators and Reflector in guards
🤔Before reading on: do you think guards can work without Reflector when using custom decorators? Commit to your answer.
Concept: Guards use Reflector to read metadata from custom decorators and decide if a request should proceed.
You write a guard that injects Reflector, reads metadata like roles from the current handler or class, and compares it with the user's roles. If the user lacks permission, the guard denies access.
Result
Your app enforces rules based on metadata, keeping authorization logic clean and centralized.
Seeing how decorators and Reflector work together in guards reveals a powerful pattern for clean, maintainable authorization.
6
AdvancedHandling metadata inheritance and overrides
🤔Before reading on: do you think metadata on a method overrides class metadata, or do they combine? Commit to your answer.
Concept: Reflector can merge metadata from classes and methods, allowing overrides or defaults.
Using Reflector's getAllAndOverride() method, you can get metadata from the method first, and if missing, fallback to class metadata. This lets you set default roles on a class and override them on specific methods.
Result
You get flexible metadata handling that supports inheritance and overrides.
Knowing how to merge metadata prevents bugs and supports DRY (Don't Repeat Yourself) principles in your code.
7
ExpertCustom decorators and Reflector internals
🤔Before reading on: do you think metadata is stored globally or per class/method? Commit to your answer.
Concept: Metadata is stored using Reflect Metadata API per target and key, and Reflector wraps this API for NestJS patterns.
When you use SetMetadata, it calls Reflect.defineMetadata with a key and value on the target (class or method). Reflector calls Reflect.getMetadata to retrieve this info. This metadata is stored in memory linked to the target, not globally. This design allows fast, scoped metadata access.
Result
You understand the low-level mechanism behind decorators and Reflector.
Understanding this internal storage clarifies why metadata is isolated per class/method and how Reflector efficiently accesses it.
Under the Hood
NestJS uses the Reflect Metadata API, a JavaScript feature that lets you attach and read metadata on objects like classes or methods. When you create a custom decorator with SetMetadata, it stores a key-value pair on the target using Reflect.defineMetadata. Later, Reflector calls Reflect.getMetadata to retrieve this data. This metadata is stored in a hidden map inside the JavaScript runtime, linked to the target object and key. This allows NestJS to keep extra info about your code without changing its behavior directly.
Why designed this way?
This design was chosen to separate metadata from code logic, making it reusable and non-intrusive. Alternatives like global variables or manual property additions would clutter code and cause conflicts. Using the Reflect Metadata API standardizes metadata handling and fits well with TypeScript decorators. NestJS wraps this API with Reflector to provide a clean, consistent way to access metadata in common patterns like guards and interceptors.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Custom       │       │ Reflect.define │       │ Metadata      │
│ Decorator   ├──────▶│ Metadata(key,  )├──────▶│ stored linked │
│ (SetMetadata)│       │ value, target) │       │ to target/key │
└───────────────┘       └───────────────┘       └───────────────┘
                                      ▲
                                      │
                              ┌───────────────┐
                              │ Reflector     │
                              │ calls        │
                              │ Reflect.get  │
                              └───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does Reflector read metadata from instances or from class definitions? Commit to your answer.
Common Belief:Reflector reads metadata from class instances at runtime.
Tap to reveal reality
Reality:Reflector reads metadata from class constructors or method definitions, not from instances.
Why it matters:If you try to read metadata from an instance, you get undefined, causing bugs in guards or interceptors.
Quick: Do custom decorators execute code when applied or only store metadata? Commit to your answer.
Common Belief:Custom decorators always run code immediately when applied.
Tap to reveal reality
Reality:Custom decorators can run code, but those using SetMetadata mainly store metadata without side effects.
Why it matters:Expecting side effects from metadata-only decorators leads to confusion and unexpected behavior.
Quick: Does metadata automatically merge from class to method? Commit to your answer.
Common Belief:Metadata on a class and its methods automatically combine without extra code.
Tap to reveal reality
Reality:Metadata does not merge automatically; you must use Reflector methods like getAllAndOverride to combine them.
Why it matters:Assuming automatic merging causes incorrect permission checks or missing metadata.
Quick: Can you use Reflector outside NestJS guards or interceptors? Commit to your answer.
Common Belief:Reflector is only useful inside guards or interceptors.
Tap to reveal reality
Reality:Reflector can be used anywhere you need to read metadata, including services or controllers.
Why it matters:Limiting Reflector use reduces flexibility and code reuse opportunities.
Expert Zone
1
Custom decorators can accept parameters and return a decorator function that sets dynamic metadata, enabling flexible and reusable metadata patterns.
2
Reflector's getAllAndOverride method respects method-level metadata first, then class-level, allowing precise control over metadata inheritance.
3
Metadata keys should be unique and well-namespaced to avoid collisions, especially in large applications or when using third-party libraries.
When NOT to use
Avoid using Reflector and custom decorators for simple flags or data that can be passed directly as method arguments or service properties. For complex state or runtime data, use dependency injection or context objects instead.
Production Patterns
In real apps, custom decorators with Reflector are commonly used for role-based access control, feature toggles, and API versioning. Guards read metadata to enforce security, while interceptors might use metadata to modify responses. This pattern centralizes cross-cutting concerns cleanly.
Connections
Aspect-Oriented Programming (AOP)
Reflector and custom decorators implement AOP-like behavior by attaching metadata that triggers cross-cutting logic.
Understanding AOP helps grasp how metadata-driven decorators separate concerns like logging or security from business logic.
Annotations in Java
Custom decorators and Reflector in NestJS are similar to Java annotations and reflection used to add and read metadata.
Knowing Java annotations clarifies how metadata can drive behavior without changing core code.
Sticky notes on documents
Metadata is like sticky notes attached to code elements, and Reflector reads these notes to guide decisions.
This connection helps understand metadata as external, non-intrusive information that enhances code meaning.
Common Pitfalls
#1Trying to read metadata from an instance instead of the class or method.
Wrong approach:const roles = reflector.get('roles', instance);
Correct approach:const roles = reflector.get('roles', instance.constructor);
Root cause:Metadata is stored on the class or method, not on instances, so reading from instance returns undefined.
#2Assuming metadata merges automatically between class and method.
Wrong approach:const roles = reflector.get('roles', context.getHandler()); // ignores class metadata
Correct approach:const roles = reflector.getAllAndOverride('roles', [context.getHandler(), context.getClass()]);
Root cause:Metadata does not merge by default; explicit merging is needed to combine class and method metadata.
#3Defining custom decorators without unique metadata keys causing collisions.
Wrong approach:SetMetadata('roles', ['admin']); // key 'roles' used by multiple decorators
Correct approach:SetMetadata('custom:roles', ['admin']); // namespaced key to avoid conflicts
Root cause:Using common keys without namespaces leads to overwriting or mixing metadata from different decorators.
Key Takeaways
Reflector and custom decorators work together to add and read hidden metadata on classes and methods in NestJS.
Custom decorators use SetMetadata to attach meaningful data, while Reflector reads this data to guide app logic like authorization.
Metadata is stored per class or method, not on instances, and does not merge automatically without explicit calls.
This pattern keeps your code clean, reusable, and easy to maintain by separating concerns and centralizing logic.
Understanding the internal use of Reflect Metadata API helps avoid common mistakes and write robust NestJS applications.