Building a Decorator Framework
Combine decorators and metadata into a mini framework.
Building a Decorator Framework is a free TypeScript Academy lesson on CoddyKit. This is lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, and your progress syncs across the web and the CoddyKit app. The TypeScript Academy course includes 4 lessons in total.
Building a Mini Framework
Now we combine decorators and metadata to build a tiny validation framework. Field decorators record rules into class metadata; a validate function reads them back and checks an instance.
The Plan
We will (1) define field decorators like @minLength and @isEmail that store rules in context.metadata, then (2) write a validate(instance) that walks those rules.
A Rule Registry Helper
Each decorator pushes a rule into a per-field list on the shared metadata object.
type Rule = (value: any) => string | null; // returns error message or null
function addRule(context: ClassFieldDecoratorContext, rule: Rule) {
const md = context.metadata as any;
md.rules ??= {};
(md.rules[context.name] ??= []).push(rule);
}Defining Validators
Concrete decorators wrap addRule with a specific check.
function minLength(n: number) {
return function (_: any, ctx: ClassFieldDecoratorContext) {
addRule(ctx, (v) =>
typeof v === "string" && v.length >= n ? null : "too short");
};
}
function isEmail(_: any, ctx: ClassFieldDecoratorContext) {
addRule(ctx, (v) => /.+@.+/.test(String(v)) ? null : "invalid email");
}Decorating a Class
Applying the decorators populates the class metadata with a rules map keyed by field name.
class SignupForm {
@minLength(3) username = "";
@isEmail email = "";
}
// SignupForm[Symbol.metadata].rules =
// { username: [rule], email: [rule] }The validate Function
validate reads the rules off the class metadata and runs each against the instance value, collecting errors.
function validate(instance: object): Record<string, string[]> {
const cls = instance.constructor as any;
const rules = (cls[Symbol.metadata]?.rules ?? {}) as Record<string, Rule[]>;
const errors: Record<string, string[]> = {};
for (const field in rules) {
const msgs = rules[field]
.map((r) => r((instance as any)[field]))
.filter((m): m is string => m !== null);
if (msgs.length) errors[field] = msgs;
}
return errors;
}Using the Framework
Construct an instance, then validate it. Invalid fields appear in the result.
const form = new SignupForm();
form.username = "ab"; // too short
form.email = "not-an-email";
console.log(validate(form));
// { username: ["too short"], email: ["invalid email"] }Extending to Routing
The same pattern builds a router: a class decorator stores a base path, method decorators store HTTP verb + subpath, and a bootstrap function reads the metadata to register handlers.
function get(path: string) {
return function (_: any, ctx: ClassMethodDecoratorContext) {
const md = ctx.metadata as any;
md.routes ??= [];
md.routes.push({ method: "GET", path, handler: ctx.name });
};
}Composing Decorators
Because rules accumulate in a list, multiple decorators on one field stack. Order of evaluation does not matter for validation since all rules run.
class Account {
@minLength(8)
@isEmail
email = ""; // both rules recorded
}Type Safety Considerations
The metadata object is loosely typed (any) at the framework boundary. Wrap reads in small typed helpers (like getMeta) so consumer code stays type-safe even though the storage is dynamic.
Why This Pattern Scales
Decorators provide a declarative surface; metadata provides a uniform place to collect intent; a single processor turns that intent into behavior. This separation is exactly how production validation, ORM, and routing frameworks are structured.
Quick Check
Verify your grasp of building a decorator framework.
Recap
You built a framework by recording rules into class metadata via field decorators and processing them in a validate function read off Symbol.metadata. The same decorate-then-process pattern extends to routing, and stacked decorators compose into rule lists.
Learn TypeScript with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 101
- Lessons
- 352
Frequently Asked Questions
Is the “Building a Decorator Framework” lesson free?
Yes — the full text of “Building a Decorator Framework” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO. The TypeScript Academy course includes 4 lessons in total.
What will I learn in “Building a Decorator Framework”?
Combine decorators and metadata into a mini framework. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
How long does the “Building a Decorator Framework” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Stage 3 Decorators Explained
- Decorator Metadata
- reflect-metadata and Design Types
- Building a Decorator Framework