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
| Option | Type | Required | Description |
|---|---|---|---|
projectKey | string | Yes | Project key from the Flagify dashboard |
publicKey | string | Yes | Public API key (safe for client-side) |
secretKey | string | No | Secret key for server-side evaluation with targeting rules |
isGlobal | boolean | No | Register module globally (default: true) |
options.realtime | boolean | No | Enable real-time flag updates via SSE |
options.staleTimeMs | number | No | Cache stale time in ms (default: 300000) |
options.apiUrl | string | No | Custom API URL for self-hosted instances |
options.pollIntervalMs | number | No | Polling interval in ms as realtime fallback |
identify | function | No | Extract 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
| Method | Signature | Description |
|---|---|---|
isEnabled | isEnabled(key: string): boolean | Check if a boolean flag is enabled |
getValue | getValue<T>(key: string, fallback: T): T | Get a typed flag value with fallback |
getVariant | getVariant(key: string, fallback: string): string | Get the winning variant key |
evaluate | evaluate(key: string, user: FlagifyUser): Promise<EvaluateResult> | Evaluate a flag with user targeting |
isReady | isReady(): boolean | Check if the client has finished initializing |
getClient | getClient(): Flagify | Access 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
| Scenario | What the guard does |
|---|---|
No @RequireFlag on handler | Allows access (passthrough) |
@RequireFlag('key') | Calls isEnabled(key) |
@RequireFlag({ key, value }) | Calls getValue(key) and compares |
@RequireFlag({ key, value }) + identify | Calls 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 };
}
}
| Usage | Behavior |
|---|---|
@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 hook | What 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';