Skip to content

Feature Flag Guard

The @FeatureFlag() decorator automatically applies UseGuards(FeatureFlagGuard), so you do not need to add @UseGuards() yourself.

Method-level

typescript
import { FeatureFlag } from '@nestarc/feature-flag';

@Controller('dashboard')
export class DashboardController {
  @FeatureFlag('NEW_DASHBOARD')
  @Get()
  getDashboard() {
    return { message: 'Welcome to the new dashboard' };
  }
}

Class-level

typescript
@FeatureFlag('BETA_API')
@Controller('beta')
export class BetaController {
  @Get('feature-a')
  featureA() { /* guarded */ }

  @Get('feature-b')
  featureB() { /* guarded */ }
}

Custom status code and fallback

typescript
@FeatureFlag('PREMIUM_FEATURE', {
  statusCode: 402,
  fallback: { message: 'Upgrade required' },
})
@Get('premium')
getPremiumContent() { ... }

When the flag is disabled, the guard responds with the given statusCode (default 403) and optional fallback body.

Bypassing the guard

Use @BypassFeatureFlag() on methods that should always be accessible, even when a class-level flag is applied:

typescript
import { BypassFeatureFlag } from '@nestarc/feature-flag';

@FeatureFlag('BETA_API')
@Controller('beta')
export class BetaController {
  @Get('docs')
  betaDocs() { /* guarded by BETA_API */ }

  @BypassFeatureFlag()
  @Get('health')
  healthCheck() {
    return { status: 'ok' };
  }
}

Programmatic Evaluation

Inject FeatureFlagService for service-layer checks outside the HTTP request cycle:

typescript
import { FeatureFlagService } from '@nestarc/feature-flag';

@Injectable()
export class PaymentService {
  constructor(private readonly flags: FeatureFlagService) {}

  async processPayment(order: Order) {
    const useNewGateway = await this.flags.isEnabled('NEW_PAYMENT_GATEWAY');

    if (useNewGateway) {
      return this.newGateway.process(order);
    }
    return this.legacyGateway.process(order);
  }
}

Evaluate all flags at once

typescript
const allFlags = await this.flags.evaluateAll();
// { NEW_DASHBOARD: true, PREMIUM_FEATURE: false, ... }

Explicit evaluation context

Both isEnabled() and evaluateAll() accept an optional EvaluationContext to override the auto-detected context:

typescript
const enabled = await this.flags.isEnabled('MY_FLAG', {
  userId: 'user-123',
  tenantId: 'tenant-abc',
  environment: 'staging',
});

Passing null explicitly clears that dimension, suppressing any ambient value from the request context:

typescript
// Evaluate as if no user is present, even within a request with x-user-id
const globalResult = await this.flags.isEnabled('MY_FLAG', { userId: null });

Released under the MIT License.