Building a Decorator Framework
Combine decorators and metadata into a mini framework.
Building a Decorator Framework is a free TypeScript Academy lesson on CoddyKit — 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, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
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.
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, and the TypeScript Academy course includes 4 lessons in total. 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.
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; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
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