NestJS Enterprise Backend APIs · レッスン

横断的な設定のためのクラスレベルDecorator

カスタムclass decoratorでcontrollerとproviderにタグを付け、feature flagやテナントスコープを制御します

レッスン 4/413 ステップ

「横断的な設定のためのクラスレベルDecorator」はCoddyKit上の無料NestJS Enterprise Backend APIsレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはNestJS Enterprise Backend APIs学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 NestJS Enterprise Backend APIsコースには全4レッスンが含まれています。

このレッスンの一部はまだ翻訳されておらず、英語で表示されています。

Why Class-Level Decorators?

In an enterprise NestJS API, some configuration applies to a whole controller or provider, not a single route. Think feature flags, tenant scoping, audit categories, or rate-limit tiers.

  • Repeating this on every method is noisy and error-prone.
  • A class-level decorator lets you tag the class once and read that tag later.

The pattern: attach metadata to the class, then read it inside a guard, interceptor, or middleware to drive cross-cutting behavior.

Decorators Are Just Functions

A class decorator is a function that receives the class constructor as its only argument. You can wrap it in a factory so callers pass options.

Here is the raw shape, with no framework involved, so you can see exactly what runs at class-definition time.

// Plain TypeScript: a class decorator factory
function Tag(label: string) {
  return function (target: Function) {
    console.log(`Decorating ${target.name} with label=${label}`);
  };
}

@Tag('billing')
class InvoiceController {}

console.log('Class defined:', InvoiceController.name);

Storing Metadata with Reflect

Logging is not useful by itself. We need to store the config so other code can read it. NestJS builds on the reflect-metadata library, which lets you attach key/value metadata to a class.

  • Reflect.defineMetadata(key, value, target) writes.
  • Reflect.getMetadata(key, target) reads.

The class itself is the storage target, so the tag travels with the type.

A Tenant-Scope Decorator

Let's build a real one: @TenantScope('strict') marks a controller so that all its routes must resolve a tenant. We define a metadata key and a factory that writes it.

In NestJS you would normally use the built-in SetMetadata helper, but writing it by hand shows what it does under the hood.

import 'reflect-metadata';

export const TENANT_SCOPE = 'tenant:scope';

export function TenantScope(mode: 'strict' | 'optional') {
  return (target: Function) => {
    Reflect.defineMetadata(TENANT_SCOPE, mode, target);
  };
}

@TenantScope('strict')
class OrdersController {}

const mode = Reflect.getMetadata(TENANT_SCOPE, OrdersController);
console.log('Tenant mode:', mode); // strict

Using SetMetadata in NestJS

NestJS ships SetMetadata(key, value) which returns a decorator usable on both classes and methods. Wrapping it in a named factory gives you a clean, self-documenting API.

This is the idiomatic way to author custom decorators in NestJS instead of calling Reflect.defineMetadata yourself.

import { SetMetadata } from '@nestjs/common';

export const FEATURE_FLAG = 'feature:flag';

// Class-level decorator built on SetMetadata
export const FeatureFlag = (flag: string) =>
  SetMetadata(FEATURE_FLAG, flag);

@FeatureFlag('beta-checkout')
export class CheckoutController {}

Reading Metadata with Reflector

To consume the tag at request time, inject NestJS's Reflector service. A guard can read the class-level metadata from context.getClass().

  • reflector.get(KEY, context.getClass()) reads the controller-level tag.
  • reflector.getAllAndOverride(KEY, [handler, class]) lets a method override the class default.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { FEATURE_FLAG } from './feature-flag.decorator';

@Injectable()
export class FeatureFlagGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const flag = this.reflector.get<string>(
      FEATURE_FLAG,
      context.getClass(),
    );
    if (!flag) return true; // no flag => always allowed
    return isFeatureEnabled(flag);
  }
}

declare function isFeatureEnabled(flag: string): boolean;

Method Overrides Class

A common enterprise need: the controller sets a default, but one route opts out. getAllAndOverride checks the handler first, then falls back to the class.

Order matters: the array is searched left to right, so put the most specific target (the method handler) first.

canActivate(context: ExecutionContext): boolean {
  const mode = this.reflector.getAllAndOverride<'strict' | 'optional'>(
    TENANT_SCOPE,
    [context.getHandler(), context.getClass()],
  );
  // method @TenantScope('optional') wins over class @TenantScope('strict')
  return mode === 'optional' ? true : this.hasTenant(context);
}

Composing Multiple Tags

Cross-cutting config often combines concerns: a feature flag and a tenant mode and an audit category. Use applyDecorators to bundle them into one expressive decorator.

This keeps controllers readable: one decorator communicates the full intent.

import { applyDecorators, SetMetadata } from '@nestjs/common';
import { FEATURE_FLAG } from './feature-flag.decorator';
import { TENANT_SCOPE } from './tenant-scope.decorator';
import { AUDIT_CATEGORY } from './audit.decorator';

export function EnterpriseModule(opts: {
  flag: string;
  tenant: 'strict' | 'optional';
  audit: string;
}) {
  return applyDecorators(
    SetMetadata(FEATURE_FLAG, opts.flag),
    SetMetadata(TENANT_SCOPE, opts.tenant),
    SetMetadata(AUDIT_CATEGORY, opts.audit),
  );
}

@EnterpriseModule({ flag: 'beta-checkout', tenant: 'strict', audit: 'orders' })
export class OrdersController {}

Tagging Providers, Not Just Controllers

Class-level decorators are not limited to controllers. You can tag any provider class and read the metadata wherever you have the class reference, for example in a factory or a discovery service.

NestJS's DiscoveryService can enumerate all providers and inspect their metadata, which is how you build registries of tagged services.

import 'reflect-metadata';

const CACHE_TIER = 'cache:tier';
function CacheTier(tier: 'hot' | 'cold') {
  return (target: Function) => Reflect.defineMetadata(CACHE_TIER, tier, target);
}

@CacheTier('hot')
class PricingService {}

@CacheTier('cold')
class ReportService {}

for (const svc of [PricingService, ReportService]) {
  const tier = Reflect.getMetadata(CACHE_TIER, svc);
  console.log(`${svc.name} -> ${tier}`);
}

Wiring the Guard Globally

For cross-cutting config to take effect everywhere, register the consuming guard or interceptor once at the module level. The guard then inspects each request's target class.

A global guard plus class-level metadata means you configure behavior declaratively on each controller, with zero per-route wiring.

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { FeatureFlagGuard } from './feature-flag.guard';

@Module({
  providers: [
    { provide: APP_GUARD, useClass: FeatureFlagGuard },
  ],
})
export class AppModule {}

Typing Metadata Keys Safely

Stringly-typed keys ('tenant:scope') are easy to mistype. Two safeguards used in enterprise codebases:

  • Export the key as a const from the decorator file so producer and consumer share one symbol.
  • Make the factory's argument a union type so invalid modes fail at compile time.

This turns config typos into TypeScript errors instead of silent runtime bugs.

type TenantMode = 'strict' | 'optional';

function validate(mode: TenantMode): TenantMode {
  return mode;
}

console.log(validate('strict'));
// validate('loose') would be a compile-time error
console.log('Allowed modes: strict | optional');

Quick Check

A controller is tagged @TenantScope('strict') at the class level, and one of its methods is tagged @TenantScope('optional'). Your guard must let that method opt out while keeping the strict default for the rest.

Recap

You learned how to drive cross-cutting config with class-level decorators:

  • Author a decorator with SetMetadata (or Reflect.defineMetadata) wrapped in a typed factory.
  • Consume the tag with Reflector from context.getClass() inside a guard or interceptor.
  • Override class defaults per-method using getAllAndOverride with the handler listed first.
  • Compose multiple concerns via applyDecorators, and tag providers too, not just controllers.
  • Register the consumer globally with APP_GUARD so the config applies declaratively across the app.

The result: feature flags and tenant scoping configured once per class, enforced everywhere.

無料で開始

AI チューターと学ぶ TypeScript — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
20
レッスン
76

よくある質問

「横断的な設定のためのクラスレベルDecorator」レッスンは無料ですか?

はい。「横断的な設定のためのクラスレベルDecorator」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、NestJS Enterprise Backend APIsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 NestJS Enterprise Backend APIsコースには全4レッスンが含まれています。

「横断的な設定のためのクラスレベルDecorator」で何を学びますか?

カスタムclass decoratorでcontrollerとproviderにタグを付け、feature flagやテナントスコープを制御します ブラウザで直接実行するハンズオンコードでNestJS Enterprise Backend APIsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

NestJS Enterprise Backend APIsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのNestJS Enterprise Backend APIsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「横断的な設定のためのクラスレベルDecorator」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このNestJS Enterprise Backend APIsレッスンでコードを書いて実行できますか?

はい。すべてのNestJS Enterprise Backend APIsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Param Decoratorでリクエストコンテキストを読み取る
  2. SetMetadataとReflectorでメタデータを付与する
  3. applyDecoratorsでDecoratorを組み合わせる
  4. 横断的な設定のためのクラスレベルDecorator
← NestJS Enterprise Backend APIsに戻る