Skip to content

Feature Flag Guard ​

This page describes published 0.5.0. Complete installation and the first HTTP request before adding more routes.

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({ userId: 'user-123' });
// { NEW_DASHBOARD: true, PREMIUM_FEATURE: false, ... }

evaluateAll() returns active stored flags only. It does not evaluate registry-only keys, emit evaluation/exposure events, or convert a database/cache error to per-flag defaults. Published 0.5.0 also omits registry bucketBy in this path; see version limits.

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 suppresses the ambient value for that dimension. The resolved attribute remains null and can still match an override containing null:

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

Released under the MIT License.