こんにちは。TypeScriptやNestJSを日常的に使っているエンジニアです。
NestJSやTypeORM、クリーンアーキテクチャ系のライブラリを触っていると、やたら出てくるこの記法:
@Controller('users')
export class UserController {
@Get(':id')
getUser(@Param('id') id: string) {
return `User ID is ${id}`;
}
}
この @Get
や @Param
、何をしてるのか説明できますか?
「なんとなく使ってるけど仕組みはよく知らない」
「NestJSで当たり前に使ってるけど、自分で作ったことはない」
――そんな方のために、TypeScriptのデコレータを基礎から応用まで本気で理解するための記事を書いてみました。
✅ デコレータとは?一言で言うと…
クラスやメソッド、プロパティなどに「機能を後付け」できる構文
JavaやPythonのアノテーションを触ったことがある方なら「ああ、あれね」と思ってもらえればOKです。
例えば、こんな風に使います:
@Log
class UserService {}
ここで @Log
は、クラス定義に割り込む関数です。定義時点でこの関数が呼ばれ、何らかの処理をします。
🎯 デコレータはどこに使えるの?
TypeScriptでは、以下の場所にデコレータを使えます:
種類 | 対象 | 例 |
---|---|---|
クラス | クラス定義 | @Controller() |
メソッド | クラスの関数 |
@Get() , @Post()
|
プロパティ | クラスのフィールド | @Column() |
パラメータ | メソッドの引数 |
@Param() , @Body()
|
⚙️ 実際どう動くの?(基礎編)
🔧 クラスデコレータの例
function Log(constructor: Function) {
console.log(`クラス ${constructor.name} が定義されました`);
}
@Log
class UserService {}
この場合、UserService
クラスが読み込まれるタイミングで、Log()
関数が呼び出されます。要するにただの関数コールなんです。
🛠 自作デコレータでメソッドにログ出してみる
繰り返し使う処理は、デコレータで共通化できます。よくあるのがログ出力系。
✨ ログ付きメソッドデコレータ
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;
};
}
✅ 使ってみる
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
というライブラリを使います。
📦 セットアップ
npm install reflect-metadata
tsconfig.json
に追記:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
ファイルの先頭で import:
import 'reflect-metadata';
🎯 型情報を取得するデコレータの例
function LogType(target: any, key: string) {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`Property "${key}" has type: ${type.name}`);
}
✅ 使用例
class Sample {
@LogType
name: string;
@LogType
age: number;
}
✅ 出力
Property "name" has type: String
Property "age" has type: Number
こうやってプロパティの型情報にアクセスできるので、DI、バリデーション、スキーマ生成などに応用できます。NestJSはこれをベースに動いてます。
📚 NestJSでのデコレータ活用の裏側(簡単に)
NestJSでは、以下のようなデコレータが大量に使われています:
@Controller('users')
export class UserController {
@Get(':id')
getUser(@Param('id') id: string) {}
}
これらはすべてメタデータとして情報を集約して、内部でルーティングや依存性注入の設定に使っているわけです。まさにデコレータの恩恵です。
✅ おさらい:デコレータの要点まとめ
ポイント | 内容 |
---|---|
何? | クラスや関数に「機能を後から追加」する仕組み |
実態 | ただの関数。コンパイル時に呼び出される |
使用場所 | クラス、メソッド、プロパティ、引数 |
応用技術 |
reflect-metadata を使えば型情報も扱える |
実例 | NestJS、TypeORM、class-validator などで活用 |
🧪 実践のすすめ
TypeScriptでデコレータを使いこなせるようになると、
- 共通処理の切り出し
- ライブラリの中身の理解
- フレームワークの構築
など、ワンランク上のコード設計ができるようになります。慣れるまでは魔法っぽいですが、「ただの関数」だと気づくと一気に楽になりますよ。