
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@iziad/feature-flags
Advanced tools
A comprehensive, type-safe feature flag library for NestJS with Redis caching and automatic DB sync
A production-ready, type-safe feature flag library for NestJS with dynamic ORM support (TypeORM & Prisma), Redis caching, and automatic database synchronization.
# Install the library
npm install @iziad/feature-flags
# Install your ORM (choose one)
npm install @nestjs/typeorm typeorm # For TypeORM
npm install @prisma/client # For Prisma
# Optional: Redis for caching and distributed locking
npm install ioredis
// src/feature-flags/flags.ts
import { flags, extractDefinitions } from '@iziad/feature-flags';
export const MyFlags = flags('ui', {
DARK_MODE: 'Enable dark mode theme',
NEW_DASHBOARD: 'New dashboard design',
});
export const ALL_FLAGS = extractDefinitions(MyFlags);
// app.module.ts
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from '@iziad/feature-flags';
import { ALL_FLAGS } from './feature-flags/flags';
// For TypeORM
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'user',
password: 'pass',
database: 'mydb',
}),
// Unified module - automatically uses TypeORM
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
database: {
type: 'typeorm', // or 'prisma'
},
autoCreateTables: true,
autoSync: true,
}),
],
})
export class AppModule {}
Or with Prisma:
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module'; // Your Prisma module
@Module({
imports: [
PrismaModule, // ← MUST import this! Provides PrismaService
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
database: {
type: 'prisma',
schema: 'feature_flags', // Optional: separate schema
},
prismaService: PrismaService, // ← Required! Pass the class reference
imports: [PrismaModule], // ← Also pass via imports option
autoCreateTables: true,
autoSync: true,
}),
],
})
export class AppModule {}
Important: You must do BOTH:
PrismaModule in your AppModule imports arrayprismaService: PrismaService (the class reference)imports: [PrismaModule] in the options (helps with dependency resolution)import { Injectable } from '@nestjs/common';
import { FeatureFlagService } from '@iziad/feature-flags';
import { MyFlags } from './feature-flags/flags';
@Injectable()
export class MyService {
constructor(private featureFlags: FeatureFlagService) {}
async doSomething() {
// Check if flag is enabled
const isDarkMode = await this.featureFlags.isEnabled(
MyFlags.DARK_MODE.category,
MyFlags.DARK_MODE.key
);
if (isDarkMode) {
// Dark mode logic
}
}
}
async doSomethingForUser(userId: string) {
const hasBetaAccess = await this.featureFlags.isEnabled(
'beta',
'beta_features',
userId
);
if (hasBetaAccess) {
// Show beta features
}
}
// Check flag state for multiple users at once
const result = await this.featureFlags.isEnabledForUsers(
'beta',
'ai_assistant',
['user1', 'user2', 'user3']
);
// Result contains:
// - userStates: { user1: true, user2: false, user3: true }
// - globalEnabled: false // Global flag state (before user overrides)
// - anyEnabled: true // true if ANY user has it enabled (OR logic)
// - allEnabled: false // true only if ALL users have it enabled (AND logic)
if (result.anyEnabled) {
// At least one user has access
}
if (result.allEnabled) {
// All users have access
}
// Using FlagObject
const result2 = await this.featureFlags.isEnabledForUsers(
MyFlags.BETA_FEATURES,
['user1', 'user2']
);
import { Controller, Get, UseGuards } from '@nestjs/common';
import { FeatureFlagGuard, FeatureFlag } from '@iziad/feature-flags';
import { MyFlags } from './feature-flags/flags';
@Controller('dashboard')
@UseGuards(FeatureFlagGuard)
export class DashboardController {
@Get('new')
@FeatureFlag(MyFlags.NEW_DASHBOARD.key, MyFlags.NEW_DASHBOARD.category)
getNewDashboard() {
return { version: 'new' };
}
}
import { RequireAllFeatures, RequireAnyFeature } from '@iziad/feature-flags';
@Controller('beta')
@UseGuards(FeatureFlagGuard)
export class BetaController {
// Require ALL flags to be enabled
@Get('feature-a')
@RequireAllFeatures([
{ flag: 'beta_features', category: 'beta' },
{ flag: 'new_dashboard', category: 'ui' }
])
getFeatureA() {
return { feature: 'A' };
}
// Require ANY flag to be enabled
@Get('feature-b')
@RequireAnyFeature([
{ flag: 'beta_features', category: 'beta' },
{ flag: 'dark_mode', category: 'ui' }
])
getFeatureB() {
return { feature: 'B' };
}
}
FeatureFlagModule.forRoot({
// Required
flags: ALL_FLAGS,
// Database
database: {
type: 'typeorm', // or 'prisma'
schema: 'feature_flags', // Optional: separate schema/database
tablePrefix: '_', // Table prefix (default: '_')
},
// For Prisma only
prismaService: PrismaService,
// For TypeORM only (optional - uses defaults if not provided)
entities: [FeatureFlagEntityModel, UserFeatureFlagOverrideModel],
connectionName: 'default',
// Table creation
autoCreateTables: true, // Default: false
// Synchronization
autoSync: true, // Default: true
syncOnlyIfNoLock: true, // Default: true
syncCooldown: 300, // Default: 300 seconds
// Environment
environment: {
current: process.env.NODE_ENV || 'development',
},
// Redis (optional)
redis: {
host: 'localhost',
port: 6379,
password: 'secret',
db: 0,
keyPrefix: 'ff:',
},
cache: {
ttl: 3600, // Cache TTL in seconds
},
// Admin API
module: {
flushEndpoint: true, // Enable admin endpoints
},
// Logging
logging: {
level: 'log', // 'error' | 'warn' | 'log' | 'debug' | 'verbose'
contexts: {
'FeatureFlagService': 'debug',
},
},
})
Feature flags are environment-aware by default. Each environment has its own flag states.
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
environment: {
current: process.env.NODE_ENV || 'development',
},
})
The library ensures only one container syncs at a time using distributed locking:
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
syncOnlyIfNoLock: true,
syncCooldown: 300, // 5 minutes
// Redis for distributed locking
redis: {
host: 'redis',
port: 6379,
},
})
The library automatically creates prefixed tables:
_feature_flags - Main feature flags_user_feature_flag_overrides - User-specific overrides_feature_flag_locks - Distributed locking_feature_flag_last_sync - Sync timestamp trackingFeatureFlagModule.forRoot({
flags: ALL_FLAGS,
database: {
schema: 'feature_flags', // PostgreSQL: separate schema
// MySQL: separate database
},
})
Override configuration using environment variables (highest priority):
FEATURE_FLAG_AUTO_SYNC=true
FEATURE_FLAG_SYNC_ONLY_IF_NO_LOCK=true
FEATURE_FLAG_SYNC_COOLDOWN=300
FEATURE_FLAG_SMART_CACHE_INVALIDATION=true
Priority: Environment Variables > Module Configuration > Defaults
Built-in admin endpoints (disabled by default in production):
POST /admin/feature-flags/flush # Flush cache
POST /admin/feature-flags/sync # Manual sync
GET /admin/feature-flags/status # Get status
PATCH /admin/feature-flags/override # Set user override
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
module: {
flushEndpoint: false,
},
})
import { FeatureFlagTestingModule } from '@iziad/feature-flags/testing';
const module = await Test.createTestingModule({
imports: [
FeatureFlagTestingModule.forRoot({
flags: ALL_FLAGS,
}),
],
}).compile();
class FeatureFlagService {
// Check if flag is enabled
async isEnabled(
category: string,
flag: string,
userId?: string
): Promise<boolean>
// Get all flags for a category
async getFlags(
category: string,
userId?: string
): Promise<Record<string, boolean>>
// Get detailed evaluation
async getFlagEvaluation(
category: string,
flag: string,
userId?: string
): Promise<FeatureFlagEvaluation>
// Sync flags to database
async syncFlags(): Promise<void>
// Flush cache
async flushCache(options?: FlushCacheOptions): Promise<FlushCacheResult>
// User overrides
async setUserOverride(userId: string, flagKey: string, enabled: boolean): Promise<void>
async removeUserOverride(userId: string, flagKey: string): Promise<void>
// Check flag for multiple users
async isEnabledForUsers(
categoryOrFlagObject: string | FlagObject,
flagOrUserIds?: string | string[],
userIds?: string[]
): Promise<MultipleUsersResult>
}
// Single flag
@FeatureFlag(flagKey, category)
// Optional flag (doesn't block if disabled)
@OptionalFeature(flagKey, category)
// All flags required
@RequireAllFeatures([{ flag, category }, ...])
// Any flag required
@RequireAnyFeature([{ flag, category }, ...])
protected: trueSolution: You must do THREE things:
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [
PrismaModule, // ← 1. Import PrismaModule in AppModule
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
database: { type: 'prisma' },
prismaService: PrismaService, // ← 2. Pass the class reference
imports: [PrismaModule], // ← 3. Also pass via imports option
}),
],
})
export class AppModule {}
Why? Prisma doesn't have official NestJS integration like TypeORM, so the library can't auto-detect your PrismaService. You must:
PrismaModule in your AppModule imports arrayprismaService: PrismaService (the class reference, not a string)imports: [PrismaModule] in the options (ensures proper dependency resolution)Solution: Enable autoCreateTables:
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
autoCreateTables: true,
})
The Issue: When you explicitly list entities in TypeOrmModule.forRoot(), TypeORM builds metadata at initialization time. Our module uses TypeOrmModule.forFeature() which should work, but if your DataSource is configured with an explicit entities array, you need to include our entities.
Solution 1 (Recommended): Use entity auto-loading with path patterns - TypeORM discovers entities automatically:
@Module({
imports: [
TypeOrmModule.forRoot({
// ... your database config
// Use path patterns - TypeORM will scan and load all .entity.ts files
entities: [__dirname + '/**/*.entity{.ts,.js}'],
// This automatically discovers:
// - Your app entities in src/**/*.entity.ts
// - Our entities via TypeOrmModule.forFeature() in FeatureFlagModule
}),
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
// No need to manually add entities - forFeature() handles registration!
}),
],
})
export class AppModule {}
How it works: When you use path patterns, TypeORM scans directories for entity files. Our module calls TypeOrmModule.forFeature([FeatureFlagEntityModel, ...]) which registers our entities with the DataSource. This works seamlessly because TypeORM supports dynamic entity registration when using path patterns.
Solution 2: If you use explicit entity arrays (from objects/maps), merge our entities array:
import { FEATURE_FLAG_ENTITIES } from '@iziad/feature-flags/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
// ... your database config
entities: [
// Your entities (from object/map)
...Object.values(entities),
// Dynamically include feature flag entities
...FEATURE_FLAG_ENTITIES,
],
}),
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
}),
],
})
export class AppModule {}
Alternative - Manual import (if you prefer):
import {
FeatureFlagEntityModel,
UserFeatureFlagOverrideModel
} from '@iziad/feature-flags/typeorm';
entities: [
...Object.values(entities),
FeatureFlagEntityModel,
UserFeatureFlagOverrideModel,
]
Why? TypeORM builds entity metadata when the DataSource initializes. If you use an explicit entities array, all entities must be listed there. TypeOrmModule.forFeature() registers repositories for DI, but TypeORM still needs the entities in the DataSource metadata. When using explicit arrays, you must include our entities.
The Issue: When using TypeOrmModule.forRootAsync() with a @Global() DatabaseModule, the DataSource provider might not be accessible to FeatureFlagTypeOrmModule.
Solution: Ensure your DatabaseModule is @Global() and import it in your app module BEFORE FeatureFlagModule:
// database.module.ts
import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Global() // ✅ Must be @Global()
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () => buildTypeOrmOptions(),
dataSourceFactory: async (options) => {
const ds = new DataSource(options);
return await ds.initialize();
},
}),
],
exports: [TypeOrmModule], // ✅ Export TypeOrmModule
})
export class DatabaseModule {}
// app.module.ts
@Module({
imports: [
DatabaseModule, // ✅ Import FIRST
// ... other modules
FeatureFlagModule.forRoot({
flags: ALL_FLAGS,
database: { type: 'typeorm' },
imports: [DatabaseModule], // ✅ Also pass via imports option
}),
],
})
export class AppModule {}
Why? The FeatureFlagTypeOrmModule needs access to the DataSource to create repositories via TypeOrmModule.forFeature(). Even though DatabaseModule is @Global(), explicitly importing it ensures proper module resolution.
Note: This issue was fixed in version 2.1.1+ by getting DataSource from repository's connection instead of injecting directly, but the above setup is still recommended for best compatibility.
The Issue: This error occurs when TypeORM tries to access a foreign key column that doesn't exist in your database schema.
Solution: This has been fixed in version 2.1.1+. The entities no longer use foreign key relationships - they use flagKey string directly. If you're using an older version, upgrade to 2.1.1 or later:
npm install @iziad/feature-flags@latest
Note: The entities use flagKey string to reference flags, not a foreign key relationship. This is by design and matches how the repository queries work. The relationship was removed because it wasn't used and caused schema issues.
Contributions are welcome! Please read CONTRIBUTING.md for details.
MIT © Ziad Saber
Built with ❤️ for the NestJS community
FAQs
A comprehensive, type-safe feature flag library for NestJS with Redis caching and automatic DB sync
The npm package @iziad/feature-flags receives a total of 7 weekly downloads. As such, @iziad/feature-flags popularity was classified as not popular.
We found that @iziad/feature-flags demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.