NestJS SDK

The @flagify/nestjs package provides a first-class NestJS integration for evaluating feature flags using dependency injection, route guards, and parameter decorators.

Installation

npm install @flagify/nestjs @flagify/node

Setup

Basic setup with forRoot

// app.module.ts
import { Module } from '@nestjs/common';
import { FlagifyModule } from '@flagify/nestjs';

@Module({
  imports: [
    FlagifyModule.forRoot({
      projectKey: 'your-project-key',
      publicKey: 'pk_dev_abc123_xxxxxxxx',
      secretKey: 'sk_dev_abc123_xxxxxxxx',
      options: {
        realtime: true,
      },
    }),
  ],
})
export class AppModule {}

Async setup with ConfigService

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { FlagifyModule } from '@flagify/nestjs';

@Module({
  imports: [
    ConfigModule.forRoot(),
    FlagifyModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        projectKey: config.getOrThrow('FLAGIFY_PROJECT_KEY'),
        publicKey: config.getOrThrow('FLAGIFY_PUBLIC_KEY'),
        secretKey: config.get('FLAGIFY_SECRET_KEY'),
        options: {
          realtime: true,
        },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Configuration options

OptionTypeRequiredDescription
projectKeystringYesProject key from the Flagify dashboard
publicKeystringYesPublic API key (safe for client-side)
secretKeystringNoSecret key for server-side evaluation with targeting rules
isGlobalbooleanNoRegister module globally (default: true)
options.realtimebooleanNoEnable real-time flag updates via SSE
options.staleTimeMsnumberNoCache stale time in ms (default: 300000)
options.apiUrlstringNoCustom API URL for self-hosted instances
options.pollIntervalMsnumberNoPolling interval in ms as realtime fallback
identifyfunctionNoExtract user context from ExecutionContext for targeting

FlagifyService

Inject FlagifyService into any controller or service to evaluate flags:

import { Controller, Get } from '@nestjs/common';
import { FlagifyService } from '@flagify/nestjs';

@Controller('dashboard')
export class DashboardController {
  constructor(private readonly flagify: FlagifyService) {}

  @Get()
  getDashboard() {
    const showNewUI = this.flagify.isEnabled('new-dashboard');
    const maxItems = this.flagify.getValue('dashboard-max-items', 10);
    const variant = this.flagify.getVariant('checkout-experiment', 'control');

    return { showNewUI, maxItems, variant };
  }
}

Available methods

MethodSignatureDescription
isEnabledisEnabled(key: string): booleanCheck if a boolean flag is enabled
getValuegetValue<T>(key: string, fallback: T): TGet a typed flag value with fallback
getVariantgetVariant(key: string, fallback: string): stringGet the winning variant key
evaluateevaluate(key: string, user: FlagifyUser): Promise<EvaluateResult>Evaluate a flag with user targeting
isReadyisReady(): booleanCheck if the client has finished initializing
getClientgetClient(): FlagifyAccess the underlying @flagify/node client

Evaluating with user context

For server-side evaluation with targeting rules, use the evaluate method with a FlagifyUser:

import { Controller, Get, Req } from '@nestjs/common';
import { FlagifyService } from '@flagify/nestjs';

@Controller('features')
export class FeaturesController {
  constructor(private readonly flagify: FlagifyService) {}

  @Get()
  async getFeatures(@Req() req: Request) {
    const user = {
      id: req['user'].id,
      email: req['user'].email,
      role: req['user'].role,
    };

    const result = await this.flagify.evaluate('premium-features', user);
    return { enabled: result.value, reason: result.reason };
  }
}

Automatic user extraction with identify

Configure an identify callback to automatically extract user context from every request:

FlagifyModule.forRoot({
  projectKey: 'your-project-key',
  publicKey: 'pk_dev_abc123_xxxxxxxx',
  identify: (ctx) => {
    const request = ctx.switchToHttp().getRequest();
    return {
      id: request.user?.id ?? 'anonymous',
      email: request.user?.email,
      role: request.user?.role,
    };
  },
});

The identify callback is used by the FeatureFlagGuard when evaluating flags with targeting rules.

Route guards

Gate access to routes based on feature flag state using @RequireFlag and FeatureFlagGuard.

Boolean flag check

import { Controller, Get, UseGuards } from '@nestjs/common';
import { RequireFlag, FeatureFlagGuard } from '@flagify/nestjs';

@Controller('beta')
@UseGuards(FeatureFlagGuard)
export class BetaController {
  @RequireFlag('beta-access')
  @Get()
  getBetaFeature() {
    return { message: 'Welcome to the beta!' };
  }
}

Value-based check

@RequireFlag({ key: 'checkout-experiment', value: 'variant-a' })
@Get('checkout-v2')
getCheckoutV2() {
  return { message: 'Variant A checkout' };
}

Guard behavior

ScenarioWhat the guard does
No @RequireFlag on handlerAllows access (passthrough)
@RequireFlag('key')Calls isEnabled(key)
@RequireFlag({ key, value })Calls getValue(key) and compares
@RequireFlag({ key, value }) + identifyCalls evaluate(key, user) with extracted user

Parameter decorators

Inject flag values directly into controller method parameters with @FeatureFlag:

import { Controller, Get } from '@nestjs/common';
import { FeatureFlag } from '@flagify/nestjs';

@Controller('products')
export class ProductsController {
  @Get()
  getProducts(
    @FeatureFlag('show-recommendations') showRecs: boolean,
    @FeatureFlag({ key: 'max-results', fallback: 20 }) maxResults: number,
  ) {
    return { showRecs, maxResults };
  }
}
UsageBehavior
@FeatureFlag('key')Returns isEnabled(key) (boolean)
@FeatureFlag({ key, fallback })Returns getValue(key, fallback) (typed)

Real-time updates

When realtime: true is enabled, the SDK connects to the Flagify API via Server-Sent Events (SSE). Flag changes are automatically reflected in subsequent evaluations without restarting the application.

FlagifyModule.forRoot({
  projectKey: 'your-project-key',
  publicKey: 'pk_dev_abc123_xxxxxxxx',
  options: {
    realtime: true,
  },
});

The connection uses exponential backoff for reconnection (1s to 30s max). As a fallback, you can also enable polling:

options: {
  realtime: true,
  pollIntervalMs: 30000, // Poll every 30 seconds as fallback
}

Testing

Use FlagifyTestingModule to test controllers and services without network calls:

import { Test } from '@nestjs/testing';
import { FlagifyTestingModule } from '@flagify/nestjs/testing';
import { DashboardController } from './dashboard.controller';

describe('DashboardController', () => {
  it('returns new UI when flag is enabled', async () => {
    const module = await Test.createTestingModule({
      imports: [
        FlagifyTestingModule.withFlags({
          'new-dashboard': true,
          'dashboard-max-items': 25,
          'checkout-experiment': 'variant-a',
        }),
      ],
      controllers: [DashboardController],
    }).compile();

    const controller = module.get(DashboardController);
    const result = controller.getDashboard();

    expect(result.showNewUI).toBe(true);
    expect(result.maxItems).toBe(25);
  });
});

The mock service supports updating flags mid-test:

const service = module.get(FlagifyService) as any;
service.setFlag('new-dashboard', false);

Lifecycle

The module manages the @flagify/node client lifecycle automatically:

NestJS hookWhat happens
Module init (onModuleInit)Calls client.ready() — waits for initial flag sync
Module destroy (onModuleDestroy)Calls client.destroy() — disconnects SSE and clears timers

TypeScript

All types are re-exported from @flagify/node:

import type {
  FlagifyModuleOptions,
  FlagifyModuleAsyncOptions,
  RequireFlagOptions,
  FeatureFlagParamOptions,
} from '@flagify/nestjs';

import type {
  FlagifyUser,
  FlagifyOptions,
  EvaluateResult,
} from '@flagify/node';