NestJS

Use Flipswitch feature flags in NestJS with dependency injection and guards

The OpenFeature NestJS SDK provides NestJS-specific decorators and modules on top of the Flipswitch server provider. Flags evaluate server-side with real-time SSE updates.

See the JavaScript SDK for full provider configuration (polling fallback, reconnection, etc.).

Requirements

  • Node.js 20+
  • NestJS 8+

Installation

npm install @flipswitch-io/server-provider @openfeature/nestjs-sdk
yarn add @flipswitch-io/server-provider @openfeature/nestjs-sdk
pnpm add @flipswitch-io/server-provider @openfeature/nestjs-sdk

@openfeature/nestjs-sdk re-exports everything from @openfeature/server-sdk. Import all OpenFeature types from @openfeature/nestjs-sdk directly.

Quick Start

Module Setup

import { Module } from '@nestjs/common';
import { OpenFeatureModule } from '@openfeature/nestjs-sdk';
import { FlipswitchServerProvider } from '@flipswitch-io/server-provider';

@Module({
  imports: [
    OpenFeatureModule.forRoot({
      defaultProvider: new FlipswitchServerProvider({
        apiKey: 'YOUR_API_KEY',
      }),
    }),
  ],
})
export class AppModule {}

Using in a Controller

import { Controller, Get } from '@nestjs/common';
import { OpenFeatureClient, Client } from '@openfeature/nestjs-sdk';

@Controller()
export class AppController {
  constructor(
    @OpenFeatureClient() private client: Client,
  ) {}

  @Get('/feature')
  async checkFeature() {
    const enabled = await this.client.getBooleanValue('new-feature', false);
    return { featureEnabled: enabled };
  }
}

Client Injection

Use the @OpenFeatureClient() decorator to inject the OpenFeature client into any service or controller:

import { Injectable } from '@nestjs/common';
import { OpenFeatureClient, Client } from '@openfeature/nestjs-sdk';

@Injectable()
export class FeatureService {
  constructor(
    @OpenFeatureClient() private client: Client,
  ) {}

  async isEnabled(flagKey: string, userId: string): Promise<boolean> {
    return this.client.getBooleanValue(flagKey, false, {
      targetingKey: userId,
    });
  }
}

Domain-Scoped Clients

Use multiple providers for different domains:

@Module({
  imports: [
    OpenFeatureModule.forRoot({
      defaultProvider: new FlipswitchServerProvider({ apiKey: 'DEFAULT_KEY' }),
      providers: {
        billing: new FlipswitchServerProvider({ apiKey: 'BILLING_KEY' }),
      },
    }),
  ],
})
export class AppModule {}

Inject a domain-scoped client:

@Injectable()
export class BillingService {
  constructor(
    @OpenFeatureClient({ domain: 'billing' }) private client: Client,
  ) {}
}

Flag Parameter Decorators

Inject flag values directly into route handler parameters as Observables:

import { Controller, Get } from '@nestjs/common';
import { map, Observable } from 'rxjs';
import { BooleanFeatureFlag, EvaluationDetails } from '@openfeature/nestjs-sdk';

@Controller()
export class AppController {
  @Get('/welcome')
  welcome(
    @BooleanFeatureFlag({ flagKey: 'new-welcome', defaultValue: false })
    flag: Observable<EvaluationDetails<boolean>>,
  ) {
    return flag.pipe(
      map((details) =>
        details.value
          ? { message: 'Welcome to the new experience!' }
          : { message: 'Welcome!' }
      ),
    );
  }
}

Available decorators:

@BooleanFeatureFlag({ flagKey: 'flag', defaultValue: false })
@StringFeatureFlag({ flagKey: 'flag', defaultValue: 'default' })
@NumberFeatureFlag({ flagKey: 'flag', defaultValue: 0 })
@ObjectFeatureFlag({ flagKey: 'flag', defaultValue: {} })

RequireFlagsEnabled Guard

Block access to routes when flags are disabled. Returns a 404 by default when any specified flag evaluates to false:

import { Controller, Get } from '@nestjs/common';
import { RequireFlagsEnabled } from '@openfeature/nestjs-sdk';

@Controller('beta')
export class BetaController {
  @Get()
  @RequireFlagsEnabled({
    flags: [{ flagKey: 'beta-access' }],
  })
  getBetaContent() {
    return { content: 'Beta feature content' };
  }
}

Custom Exception

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

@RequireFlagsEnabled({
  flags: [
    { flagKey: 'premium-feature' },
    { flagKey: 'feature-v2', defaultValue: true },
  ],
  exception: new ForbiddenException('Feature not available'),
})

With Context

import { RequireFlagsEnabled } from '@openfeature/nestjs-sdk';

@RequireFlagsEnabled({
  flags: [{ flagKey: 'premium-feature' }],
  contextFactory: (executionContext) => {
    const request = executionContext.switchToHttp().getRequest();
    return {
      targetingKey: request.user?.id,
      plan: request.user?.plan,
    };
  },
})

Apply at the class level to gate an entire controller:

@Controller('experimental')
@RequireFlagsEnabled({
  flags: [{ flagKey: 'experimental-api' }],
})
export class ExperimentalController {
  // All routes require 'experimental-api' to be enabled
}

Evaluation Context

Per-Request Context

The module registers a global interceptor by default that propagates evaluation context through the request lifecycle. Provide a contextFactory to extract context from each request:

OpenFeatureModule.forRoot({
  defaultProvider: new FlipswitchServerProvider({ apiKey: 'YOUR_API_KEY' }),
  contextFactory: (executionContext) => {
    const request = executionContext.switchToHttp().getRequest();
    return {
      targetingKey: request.user?.id,
      email: request.user?.email,
      plan: request.user?.plan,
    };
  },
})

Static Context

For context that doesn't change per request:

OpenFeatureModule.forRoot({
  defaultProvider: new FlipswitchServerProvider({ apiKey: 'YOUR_API_KEY' }),
  context: {
    environment: 'production',
    region: 'eu-west-1',
  },
})

Disabling the Global Interceptor

If you prefer to set context manually, disable the global interceptor:

OpenFeatureModule.forRoot({
  defaultProvider: new FlipswitchServerProvider({ apiKey: 'YOUR_API_KEY' }),
  useGlobalInterceptor: false,
})

Real-Time Updates

The Flipswitch server provider maintains an SSE connection for instant flag updates. No additional NestJS configuration is needed. The provider is automatically shut down when the NestJS application stops.

Configuration Reference

OpenFeatureModule.forRoot() accepts:

OptionTypeDefaultDescription
defaultProviderProvider-The default OpenFeature provider
providersRecord<string, Provider>-Domain-scoped providers
contextEvaluationContext-Static evaluation context
contextFactory(ctx: ExecutionContext) => EvaluationContext-Per-request context factory
useGlobalInterceptorbooleantrueRegister context interceptor globally
hooksHook[]-OpenFeature hooks
handlers[ServerProviderEvents, EventHandler][]-Event handlers
loggerLogger-Custom logger

On this page