TypeScriptのデコレータ、なんとなく使ってない？仕組みから自作・応用まで本質的に理解する

こんにちは。TypeScriptやNestJSを日常的に使っているエンジニアです。

NestJSやTypeORM、クリーンアーキテクチャ系のライブラリを触っていると、やたら出てくるこの記法：

```ts
@Controller('users')
export class UserController {
  @Get(':id')
  getUser(@Param('id') id: string) {
    return `User ID is ${id}`;
  }
}
```

この `@Get` や `@Param`、**何をしてるのか説明できますか？**

「なんとなく使ってるけど仕組みはよく知らない」
「NestJSで当たり前に使ってるけど、自分で作ったことはない」
――そんな方のために、**TypeScriptのデコレータを基礎から応用まで本気で理解するための記事**を書いてみました。

---

## ✅ デコレータとは？一言で言うと…

> **クラスやメソッド、プロパティなどに「機能を後付け」できる構文**

JavaやPythonのアノテーションを触ったことがある方なら「ああ、あれね」と思ってもらえればOKです。

例えば、こんな風に使います：

```ts
@Log
class UserService {}
```

ここで `@Log` は、**クラス定義に割り込む関数**です。定義時点でこの関数が呼ばれ、何らかの処理をします。

---

## 🎯 デコレータはどこに使えるの？

TypeScriptでは、以下の場所にデコレータを使えます：

| 種類    | 対象        | 例                     |
| ----- | --------- | --------------------- |
| クラス   | クラス定義     | `@Controller()`       |
| メソッド  | クラスの関数    | `@Get()`, `@Post()`   |
| プロパティ | クラスのフィールド | `@Column()`           |
| パラメータ | メソッドの引数   | `@Param()`, `@Body()` |

---

## ⚙️ 実際どう動くの？（基礎編）

### 🔧 クラスデコレータの例

```ts
function Log(constructor: Function) {
  console.log(`クラス ${constructor.name} が定義されました`);
}

@Log
class UserService {}
```

この場合、`UserService` クラスが読み込まれるタイミングで、`Log()` 関数が呼び出されます。**要するにただの関数コール**なんです。

---

## 🛠 自作デコレータでメソッドにログ出してみる

繰り返し使う処理は、デコレータで共通化できます。よくあるのがログ出力系。

### ✨ ログ付きメソッドデコレータ

```ts
function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`[LOG] ${propertyKey} called with:`, args);
    const result = original.apply(this, args);
    console.log(`[LOG] ${propertyKey} returned:`, result);
    return result;
  };
}
```

### ✅ 使ってみる

```ts
class UserService {
  @LogMethod
  getUser(id: number) {
    return { id, name: 'Taro' };
  }
}

const svc = new UserService();
svc.getUser(42);
```

### ✅ 出力

```
[LOG] getUser called with: [ 42 ]
[LOG] getUser returned: { id: 42, name: 'Taro' }
```

ログ機能がきれいに分離できました。**何度も使い回せるように抽象化できるのがポイント**です。

---

## 🔍 応用編：reflect-metadata を使って型情報を扱う

NestJSやTypeORMは、デコレータに**型情報**を持たせて処理しています。TypeScript単体ではこれはできないので、`reflect-metadata` というライブラリを使います。

### 📦 セットアップ

```bash
npm install reflect-metadata
```

`tsconfig.json` に追記：

```json
{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}
```

ファイルの先頭で import：

```ts
import 'reflect-metadata';
```

---

### 🎯 型情報を取得するデコレータの例

```ts
function LogType(target: any, key: string) {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`Property "${key}" has type: ${type.name}`);
}
```

### ✅ 使用例

```ts
class Sample {
  @LogType
  name: string;

  @LogType
  age: number;
}
```

### ✅ 出力

```
Property "name" has type: String
Property "age" has type: Number
```

こうやって**プロパティの型情報にアクセスできる**ので、DI、バリデーション、スキーマ生成などに応用できます。NestJSはこれをベースに動いてます。

---

## 📚 NestJSでのデコレータ活用の裏側（簡単に）

NestJSでは、以下のようなデコレータが大量に使われています：

```ts
@Controller('users')
export class UserController {
  @Get(':id')
  getUser(@Param('id') id: string) {}
}
```

これらはすべて**メタデータとして情報を集約して、内部でルーティングや依存性注入の設定に使っている**わけです。まさにデコレータの恩恵です。

---

## ✅ おさらい：デコレータの要点まとめ

| ポイント | 内容                                   |
| ---- | ------------------------------------ |
| 何？   | クラスや関数に「機能を後から追加」する仕組み               |
| 実態   | ただの関数。コンパイル時に呼び出される                  |
| 使用場所 | クラス、メソッド、プロパティ、引数                    |
| 応用技術 | `reflect-metadata` を使えば型情報も扱える       |
| 実例   | NestJS、TypeORM、class-validator などで活用 |

---

## 🧪 実践のすすめ

TypeScriptでデコレータを使いこなせるようになると、

* 共通処理の切り出し
* ライブラリの中身の理解
* フレームワークの構築

など、ワンランク上のコード設計ができるようになります。慣れるまでは魔法っぽいですが、「ただの関数」だと気づくと一気に楽になりますよ。

---

## 📎 参考リンク

* [TypeScript Decorators（公式）](https://www.typescriptlang.org/docs/handbook/decorators.html)
* [reflect-metadata GitHub](https://github.com/rbuckton/reflect-metadata)
* [NestJS公式ドキュメント](https://docs.nestjs.com/)