RizTech Academy logo
RizTech Academy
Classes, Modules and DecoratorsLesson 4 of 430 min

Decorators: what they are, ahead of NestJS

Decorators are special annotations — @Something — you attach to classes, methods, or properties to add behaviour or metadata. You will meet them constantly in Angular and NestJS (and this is the main reason the Full-Stack course, which uses NestJS, needs you to understand them). This lesson demystifies what a decorator is, so that when you see @Injectable() or @Get() in framework code, it is not magic — it is a function.

What a decorator is

A decorator is, at heart, a function that receives what it decorates and can modify it or attach information to it. The @ syntax is sugar for calling that function on the thing below it:

@Component({ selector: "app-root" })    // this is a decorator applied to the class
class AppComponent { }

@Component({...}) calls a Component function, passing it the AppComponent class (and here, some configuration { selector: "app-root" }). The function can then register the class, attach metadata to it, or wrap it — whatever the framework needs. The decorator runs when the class is defined, once. So a decorator is not new syntax to fear; it is a function call in disguise, positioned above the thing it acts on.

Why frameworks love them

Decorators let a framework add a lot of behaviour with a tiny, readable annotation. Compare what @Injectable() in NestJS means — "register this class so the framework can create and inject it" — against the amount of wiring you would write by hand. The decorator hides that wiring behind one line:

// NestJS — decorators everywhere, each hiding real machinery
@Injectable()
class UserService {
  findAll(): User[] { return []; }
}

@Controller("users")
class UserController {
  constructor(private userService: UserService) {}   // injected — the framework provides it

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return `user ${id}`;
  }
}

Read it: @Controller("users") registers a controller for the /users route, @Get() maps a method to a GET request, @Param("id") extracts a route parameter. Each is a function the framework provides, and together they declare an entire HTTP API declaratively. This is the shape of a NestJS backend — the Full-Stack course is built on exactly this, and now you can see decorators for what they are: functions that wire your classes into the framework.

The kinds of decorator

Decorators can be applied to several things, and each kind of decorator receives different arguments:

@ClassDecorator                 // on a class — receives the class
class Example {
  @PropertyDecorator            // on a property
  name!: string;

  @MethodDecorator              // on a method
  doThing() {}

  method(@ParameterDecorator id: string) {}   // on a parameter
}
  • Class decorators receive the class — used to register, wrap, or add metadata to it (@Injectable, @Component).
  • Method decorators receive the method — used to wrap it (logging, caching, access control, route mapping like @Get).
  • Property and parameter decorators attach metadata to a property or parameter (@Input, @Param).

You will mostly use framework decorators, not write your own — but knowing the kinds explains why @Get() (method), @Controller() (class), and @Param() (parameter) appear in different positions.

Writing a simple one, to demystify

To prove there is no magic, here is a method decorator that logs calls — this is the whole idea:

function logged(originalMethod: any, context: any) {
  return function (this: any, ...args: any[]) {
    console.log(`Calling ${String(context.name)} with`, args);
    return originalMethod.call(this, ...args);
  };
}

class Calculator {
  @logged
  add(a: number, b: number): number {
    return a + b;
  }
}

new Calculator().add(2, 3);   // logs "Calling add with [2, 3]", then returns 5

logged receives the original add method and returns a new function that logs, then calls the original. Applying @logged above add replaces add with the wrapped version. That is all a decorator does — receive something, return a modified version (or record metadata). The framework decorators do far more sophisticated things, but the mechanism is exactly this.

The important caveats

Two things you must know before relying on decorators:

There are two versions, and they differ. Decorators were a long-standing experimental TypeScript feature (enabled by "experimentalDecorators": true in tsconfig), and Angular and current NestJS use that version, often alongside "emitDecoratorMetadata": true (which emits type information decorators can read, powering dependency injection). Meanwhile, a standardised decorators proposal reached JavaScript and modern TypeScript (5.0+) supports it without the experimental flag — but its shape is different, and the frameworks have not all moved to it. So which decorators you write depends on your framework's setup. For NestJS/Angular today, you enable experimentalDecorators and emitDecoratorMetadata; the framework's docs tell you the exact config. This is a real source of confusion, and the honest answer is "follow your framework's required tsconfig".

Decorators are one of the few TypeScript features with runtime effect. Unlike types (erased), decorators run — they are real code that executes when classes are defined, so they add to your bundle and have runtime behaviour. This is why they can do dependency injection and routing that types cannot.

The realistic takeaway

For this foundation course, the goal is recognition, not authorship. You will rarely write a custom decorator; you will constantly use framework ones. So the thing to carry forward is: a decorator is a function applied with @ that wires your class into a framework — @Injectable(), @Controller(), @Get() are functions that register and configure your code. When you reach the Full-Stack course and see a NestJS controller full of decorators, you will read it as "these annotations connect this class to the HTTP framework", not as inexplicable magic. That understanding is the whole point of this lesson — and it is exactly the bridge from TypeScript-the-language to the Android-of-the-backend that NestJS is.

Check your work

What a decorator is. A function applied with @ that receives what it decorates (a class, method, property, or parameter) and can modify it or attach metadata.

When a decorator runs. When the thing it decorates is defined — once, at definition time.

Why frameworks use them. To add a lot of behaviour (registration, injection, routing) with one readable annotation, hiding the wiring.

The kinds. Class, method, property, and parameter decorators — each receiving different arguments, which is why @Controller (class), @Get (method), and @Param (parameter) sit in different positions.

What a method decorator like logged does. Receives the original method and returns a wrapped version — the whole mechanism.

The two-versions caveat. An older experimentalDecorators version (used by Angular/NestJS, often with emitDecoratorMetadata) and a newer standardised version (TS 5+, no flag) that differ — follow your framework's required tsconfig.

Decorators and runtime. Unlike types, decorators run — real code with runtime effect and bundle cost, which is how they do injection and routing.

The takeaway for this course. Recognition, not authorship — a decorator wires your class into a framework; you will read NestJS/Angular decorators, rarely write your own.

Practice

  1. Explain, in one sentence, what @Component({ selector: "app-root" }) does to the class below it.
  2. Read the NestJS controller example and describe what each decorator (@Controller, @Get, @Param) contributes.
  3. Write the standard-form logged method decorator (the (originalMethod, context) shape shown above, which runs on modern TypeScript with no flag). Confirm it logs and still returns the right value. Then note that the experimental form Angular/NestJS use has a different signature and needs experimentalDecorators.
  4. Identify which of @Injectable, @Get, @Input, @Param are class, method, property, or parameter decorators.
  5. Explain why decorators, unlike types, have a runtime cost.
  6. Describe the two decorator versions and why your tsconfig depends on your framework.
  7. Find a decorator in framework code (or the example here) and describe it as "a function that ..." to prove it is not magic.

Official documentation

Next module — Writing TypeScript Worth Reading: the difference between code that compiles and code worth hiring.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship