TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
200: Metadata Reflection with Decorators
When most developers first dive into decorators, they assume that TypeScript's type system is still alive and kicking at runtime. I see this constantly: someone writes a decorator and expects to be able to ask, "Hey, is this property a string or a number?" and expects a straight answer. They think the decorator has a magical window into the TypeScript compiler's soul.
The "Magic Type" Myth
Let's look at why that's a trap. You might try to write something like this, thinking you can create a generic validation decorator:
function ValidateType(target: any, key: string) {
// You'd expect to find the type here, right?
const type = Reflect.getMetadata("design:type", target, key);
console.log(`Property ${key} is of type: ${type}`);
}
class User {
@ValidateType
username: string;
}
If you run this in a standard TypeScript environment, type will be undefined. Why? Because TypeScript is a compiled language. Once the code hits the browser or Node.js, all those beautiful type annotations—the : string and : number—are stripped away. They are literally deleted. Your JavaScript runtime has no idea that username was ever intended to be a string.
Bridging the Gap with Reflect-Metadata
To actually get metadata reflection, we have to explicitly tell TypeScript to stop deleting that information and instead "bake" it into the JavaScript output. You need two things: the reflect-metadata polyfill and a specific flag in your tsconfig.json.
- First, run
npm install reflect-metadata. - Second, set
"emitDecoratorMetadata": trueand"experimentalDecorators": truein yourtsconfig.json.
Now, TypeScript does something interesting. When it sees a decorator on a property, it automatically injects metadata using the Reflect API. Specifically, it creates keys like design:type, design:paramtypes, and design:returntype. I've found that relying on these "design" keys is the secret sauce for building things like Dependency Injection containers or automated API validators.
Defining Your Own Custom Metadata
While the design:type is useful, you usually want to store your own custom business logic. This is where Reflect.defineMetadata comes into play. Instead of just reacting to the type, you can attach your own "tags" to classes or methods.
Let's build a real-world example: a Required decorator that marks specific fields for a validation engine to check later.
import "reflect-metadata";
const REQUIRED_METADATA_KEY = Symbol("required");
function Required(target: any, propertyKey: string) {
// We aren't changing the property; we're just tagging it.
Reflect.defineMetadata(REQUIRED_METADATA_KEY, true, target, propertyKey);
}
class UserProfile {
@Required
email: string;
username: string; // Not required
}
function validate(obj: any) {
for (let key in obj) {
const isRequired = Reflect.getMetadata(REQUIRED_METADATA_KEY, obj, key);
if (isRequired && !obj[key]) {
throw new Error(`Field ${key} is required but missing!`);
}
}
}
const user = new UserProfile();
user.username = "dev_pro";
// user.email is missing!
validate(user); // Throws: Field email is required but missing!
Notice what happened here. The @Required decorator didn't actually "do" anything to the UserProfile class at the moment it was defined. It simply left a note in the metadata store. The validate function then came along later, read those notes, and enforced the logic. This separation of declaration (the decorator) and execution (the validator) is exactly how frameworks like NestJS or Angular handle their heavy lifting.
📋 Practical Task
Build a Metadata-Driven Role Access Guard
Your task is to create a system that restricts access to class methods based on user roles. You will need to use reflect-metadata to store the required role for each method and a guard function to check the user's role against that metadata.
Requirements:
- Create a
@Role(roleName: string)decorator that attaches the role name to the method usingReflect.defineMetadata. - Create a class
AdminPanelwith two methods:deleteUser()(requires the role 'admin') andviewDashboard()(requires the role 'user'). - Implement a function
executeSecurely(instance: any, methodName: string, userRole: string). This function should:- Retrieve the required role for the method using
Reflect.getMetadata. - If the method has a required role and the
userRoledoesn't match, throw an error:"Access Denied: Insufficient Permissions". - If the roles match (or no role is required), execute the method and return its result.
- Retrieve the required role for the method using
Test Case: Try calling executeSecurely(panel, 'deleteUser', 'user') to ensure it blocks the call, and executeSecurely(panel, 'deleteUser', 'admin') to ensure it allows it.
There are no comments for now.