TypeScript Academy · Lesson

Building a Decorator Framework

Combine decorators and metadata into a mini framework.

Lesson 4 of 413 steps

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.

Free to start

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

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