npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@iziad/feature-flags

v2.0.2

Published

A comprehensive, type-safe feature flag library for NestJS with Redis caching and automatic DB sync

Downloads

24

Readme

🚩 NestJS Feature Flags

A production-ready, type-safe feature flag library for NestJS with dynamic ORM support (TypeORM & Prisma), Redis caching, and automatic database synchronization.

npm version License: MIT


✨ Features

  • 🎯 Dynamic ORM Support - Single module automatically selects TypeORM or Prisma
  • 🔒 Type-Safe API - Full TypeScript support with autocomplete
  • 👤 User Overrides - Per-user feature flag customization
  • 🌍 Environment-Aware - Different flag states per environment
  • Redis Caching - Optional high-performance caching
  • 🔄 Auto-Sync - Automatic database synchronization with multi-container safety
  • 🛡️ Protected Flags - Prevent critical flags from deletion
  • 📦 Zero Config - Works out of the box with sensible defaults

📦 Installation

# 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

🚀 Quick Start

Step 1: Define Your Flags

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

Step 2: Setup Module (Unified API)

// 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:

  1. ✅ Import PrismaModule in your AppModule imports array
  2. ✅ Pass prismaService: PrismaService (the class reference)
  3. ✅ Pass imports: [PrismaModule] in the options (helps with dependency resolution)

💡 Usage

Check Feature Flags

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

With User Overrides

async doSomethingForUser(userId: string) {
  const hasBetaAccess = await this.featureFlags.isEnabled(
    'beta',
    'beta_features',
    userId
  );
  
  if (hasBetaAccess) {
    // Show beta features
  }
}

Route Guards

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

Multiple Flags

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

🔧 Configuration

Complete Configuration Example

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',
    },
  },
})

🌍 Environment-Aware Flags

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',
  },
})

🔐 Multi-Container Safety

The library ensures only one container syncs at a time using distributed locking:

  • Redis Lock (preferred): Uses Redis if configured
  • Database Lock (fallback): Uses database-based locks
  • Sync Cooldown: Prevents redundant syncs (default: 5 minutes)
FeatureFlagModule.forRoot({
  flags: ALL_FLAGS,
  syncOnlyIfNoLock: true,
  syncCooldown: 300, // 5 minutes
  
  // Redis for distributed locking
  redis: {
    host: 'redis',
    port: 6379,
  },
})

🗄️ Database Tables

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 tracking

Separate Schema/Database

FeatureFlagModule.forRoot({
  flags: ALL_FLAGS,
  database: {
    schema: 'feature_flags', // PostgreSQL: separate schema
                              // MySQL: separate database
  },
})

📝 Environment Variables

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


🔌 Admin API

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

Disable Admin API

FeatureFlagModule.forRoot({
  flags: ALL_FLAGS,
  module: {
    flushEndpoint: false,
  },
})

🧪 Testing

import { FeatureFlagTestingModule } from '@iziad/feature-flags/testing';

const module = await Test.createTestingModule({
  imports: [
    FeatureFlagTestingModule.forRoot({
      flags: ALL_FLAGS,
    }),
  ],
}).compile();

📚 API Reference

FeatureFlagService

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

Decorators

// 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 }, ...])

🎯 Best Practices

  1. Use Flag Objects - Type-safe with autocomplete
  2. Define Flags Centrally - Single source of truth
  3. Enable Redis in Production - Better performance
  4. Use Sync Cooldown - Prevent excessive syncs
  5. Protect Critical Flags - Mark as protected: true
  6. Environment-Aware Configuration - Different states per environment

🐛 Troubleshooting

Prisma: "Can't resolve dependencies of FEATURE_FLAG_REPOSITORY"

Solution: 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:

  1. ✅ Import PrismaModule in your AppModule imports array
  2. ✅ Pass prismaService: PrismaService (the class reference, not a string)
  3. ✅ Pass imports: [PrismaModule] in the options (ensures proper dependency resolution)

Tables Not Created

Solution: Enable autoCreateTables:

FeatureFlagModule.forRoot({
  flags: ALL_FLAGS,
  autoCreateTables: true,
})

TypeORM: "No metadata for FeatureFlagEntityModel was found"

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.


📖 Documentation


🤝 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details.


📄 License

MIT © Ziad Saber


🔗 Links


Built with ❤️ for the NestJS community