0Pricing
TypeScript Academy · Lesson

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

  1. Stage 3 Decorators Explained
  2. Decorator Metadata
  3. reflect-metadata and Design Types
  4. Building a Decorator Framework
← Back to TypeScript Academy