Get started
Back to blog

How to use feature flags in NestJS

NestJS has a strong opinion about architecture: modules, dependency injection, decorators. Your feature flag setup should follow the same patterns instead of fighting them.

This guide shows how to integrate Flagify into a NestJS application using @flagify/nestjs — a module that gives you injectable services, route guards, and parameter decorators out of the box.

Install

npm install @flagify/nestjs @flagify/node

Register the module

Add FlagifyModule to your root module. The simplest setup uses forRoot:

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 {}

The module registers globally by default — every controller and service can inject FlagifyService without re-importing the module.

Using ConfigService

In production, pull keys from environment variables:

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 {}

Evaluate flags in a service

Inject FlagifyService and call it like any other dependency:

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);

    return { showNewUI, maxItems };
  }
}

The three methods you will use most:

MethodReturnsUse case
isEnabled(key)booleanOn/off toggles
getValue(key, fallback)TTyped config values
getVariant(key, fallback)stringA/B test variants

Always provide a fallback. If the flag does not exist or the client is still initializing, the fallback is returned.

Gate routes with guards

Instead of writing if statements in every handler, use @RequireFlag to gate entire routes:

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!' };
  }
}

If the flag is disabled, the guard returns a 403 Forbidden. No conditional logic in the handler.

You can also match specific values:

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

Inject flags as parameters

For simple cases, skip the service entirely and inject flag values directly into handler parameters:

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 };
  }
}

This keeps handlers clean when all you need is a flag value.

User targeting

For percentage rollouts or attribute-based targeting, you need user context. Configure an identify callback when registering the module:

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

Now the FeatureFlagGuard automatically extracts the user from each request and evaluates flags with targeting rules. No extra code in your controllers.

For manual evaluation with user context, use the evaluate method:

@Get()
async getFeatures(@Req() req: Request) {
  const result = await this.flagify.evaluate('premium-features', {
    id: req['user'].id,
    email: req['user'].email,
  });

  return { enabled: result.value, reason: result.reason };
}

Testing

FlagifyTestingModule lets you test controllers 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,
        }),
      ],
      controllers: [DashboardController],
    }).compile();

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

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

You can also update flags mid-test:

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

Real-time updates

With realtime: true, the SDK connects via SSE and flag changes are reflected immediately — no restart needed. The connection uses exponential backoff (1s to 30s) for reconnection.

Without realtime, flags are fetched once at startup and cached. Use staleTimeMs to control how long cached values are considered fresh.

Summary

FeatureHow
Evaluate flagsInject FlagifyService
Gate routes@RequireFlag + FeatureFlagGuard
Inject values@FeatureFlag parameter decorator
User targetingidentify callback or evaluate() method
TestingFlagifyTestingModule.withFlags()

The NestJS SDK follows the framework’s conventions. Flags are injectable, testable, and declarative. No workarounds, no global singletons.

Read the full NestJS SDK reference for all options.