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

nestjs-feature-config

v1.0.1

Published

A powerful NestJS Dynamic Module for type-safe feature configuration management with conditional validation. Build robust, scalable applications with validated configuration schemas that adapt to your deployment environment.

Downloads

17

Readme

NestJS Feature Config

npm version License: MIT TypeScript npm downloads

A powerful NestJS Dynamic Module for type-safe feature configuration management with conditional validation. Build robust, scalable applications with validated configuration schemas that adapt to your deployment environment.

🚀 Features

  • 🛡️ Type-Safe: Full TypeScript support with strong typing
  • 🔧 Conditional Validation: Advanced validation with @IsRequiredIf decorator
  • 🌟 Environment-Based Validation: Revolutionary @IsRequiredInEnv decorator for environment-specific requirements
  • ⚡ Dynamic Modules: Easy registration and dependency injection
  • 🌍 Environment-Aware: Seamless environment variable integration
  • 🎯 Feature-Based: Organize configuration by features, not files
  • 🧪 Testing-Friendly: Built-in support for testing with custom environments
  • 📦 Zero Dependencies: No additional runtime dependencies beyond NestJS and class-validator
  • 🔒 Immutable: Optional configuration freezing for runtime safety
  • 💥 Fail-Fast: Application stops immediately on configuration errors

📦 Installation

Using npm

npm install nestjs-feature-config class-validator class-transformer

Using yarn

yarn add nestjs-feature-config class-validator class-transformer

Using pnpm

pnpm add nestjs-feature-config class-validator class-transformer

🏃 Quick Start

1. Create a Configuration Schema

import { IsString, IsOptional, IsEnum } from 'class-validator';
import { IsRequiredIf, IsRequiredInEnv, ConditionHelpers } from 'nestjs-feature-config';

export enum StorageProvider {
  LOCAL = 'local',
  S3 = 's3',
  AZURE = 'azure',
}

export class StorageConfigSchema {
  @IsEnum(StorageProvider)
  provider: StorageProvider;

  @IsOptional()
  @IsString()
  baseDir?: string = '/uploads';

  // 🌟 REVOLUTIONARY: Required only in production environment
  @IsRequiredInEnv('production', 'NODE_ENV', {
    message: 'AWS Access Key is required in production environment',
  })
  @IsOptional()
  @IsString()
  awsAccessKeyId?: string;

  // Required only when using S3 provider
  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
    message: 'AWS Secret Key is required when using S3 provider',
  })
  @IsOptional()
  @IsString()
  awsSecretAccessKey?: string;

  // Required only when using S3 provider
  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
    message: 'S3 bucket name is required when using S3 provider',
  })
  @IsOptional()
  @IsString()
  s3BucketName?: string;
}

2. Register the Configuration Module

import { Module } from '@nestjs/common';
import { FeatureConfigModule } from 'nestjs-feature-config';
import { StorageConfigSchema } from './config/storage-config.schema';

@Module({
  imports: [
    FeatureConfigModule.register({
      featureName: 'STORAGE',
      schema: StorageConfigSchema,
    }),
  ],
})
export class AppModule {}

3. Inject and Use Configuration

import { Injectable, Inject } from '@nestjs/common';
import { generateConfigToken } from 'nestjs-feature-config';
import { StorageConfigSchema } from './config/storage-config.schema';

@Injectable()
export class StorageService {
  constructor(
    @Inject(generateConfigToken('STORAGE'))
    private readonly config: StorageConfigSchema,
  ) {}

  getStorageInfo() {
    return {
      provider: this.config.provider,
      baseDir: this.config.baseDir,
      isS3: this.config.provider === 's3',
      bucketName: this.config.s3BucketName,
    };
  }

  async uploadFile(file: Buffer, filename: string) {
    switch (this.config.provider) {
      case 'local':
        return this.uploadToLocal(file, filename);
      case 's3':
        return this.uploadToS3(file, filename);
      default:
        throw new Error(`Unsupported provider: ${this.config.provider}`);
    }
  }

  private uploadToS3(file: Buffer, filename: string) {
    // Use this.config.awsAccessKeyId, this.config.s3BucketName, etc.
    // TypeScript knows these are available because validation passed
  }
}

4. Set Environment Variables

# For local storage
STORAGE_PROVIDER=local
STORAGE_BASE_DIR=/custom/uploads

# For S3 storage
STORAGE_PROVIDER=s3
STORAGE_AWS_ACCESS_KEY_ID=your-access-key
STORAGE_AWS_SECRET_ACCESS_KEY=your-secret-key
STORAGE_S3_BUCKET_NAME=my-app-bucket

🎯 Advanced Usage

🌟 Revolutionary Environment-Based Validation with @IsRequiredInEnv

The @IsRequiredInEnv decorator solves a critical problem in modern applications: different validation requirements for different environments. This decorator ensures fields are required only in specific environments, preventing configuration errors and improving developer experience.

import { IsString, IsOptional } from 'class-validator';
import { IsRequiredInEnv } from 'nestjs-feature-config';

export class ApiConfigSchema {
  // 🔥 Revolutionary: API key required ONLY in production
  @IsRequiredInEnv('production')
  @IsOptional()
  @IsString()
  apiKey?: string;

  // 🔥 Database URL required in production and staging
  @IsRequiredInEnv('staging')
  @IsRequiredInEnv('production')
  @IsOptional()
  @IsString()
  databaseUrl?: string;

  // 🔥 Custom environment variable name
  @IsRequiredInEnv('production', 'APP_ENV')
  @IsOptional()
  @IsString()
  secretKey?: string;

  // Debug mode (should be false in production)
  @IsOptional()
  @IsBoolean()
  debugMode?: boolean = false;
}

Benefits:

  • Development Freedom: No API keys required in development
  • Production Safety: Critical fields are enforced in production
  • Clear Error Messages: Know exactly what's missing and why
  • Fail-Fast: Application won't start with invalid configuration
  • Type Safety: Full TypeScript support

Environment Variables:

# Development - minimal requirements
NODE_ENV=development

# Production - all critical fields required
NODE_ENV=production
API_API_KEY=your-production-api-key
API_DATABASE_URL=postgresql://prod-server/db
API_SECRET_KEY=your-secret-key

Conditional Validation with Multiple Conditions

export class EmailConfigSchema {
  @IsEnum(EmailProvider)
  provider: EmailProvider;

  @IsEnum(Environment)
  environment: Environment;

  // Required in production for cloud providers
  @IsRequiredIf({
    condition: [
      ConditionHelpers.propertyIn('provider', ['sendgrid', 'mailgun']),
      ConditionHelpers.propertyEquals('environment', Environment.PRODUCTION),
    ],
    operator: LogicalOperator.AND,
    message: 'API key is required for cloud email providers in production',
  })
  @IsOptional()
  @IsString()
  apiKey?: string;

  // Required when encryption is enabled OR in production
  @IsRequiredIf({
    condition: [
      ConditionHelpers.propertyIsTruthy('enableEncryption'),
      ConditionHelpers.propertyEquals('environment', Environment.PRODUCTION),
    ],
    operator: LogicalOperator.OR,
    message: 'Certificate path required when encryption is enabled or in production',
  })
  @IsOptional()
  @IsString()
  certificatePath?: string;
}

Custom Condition Functions

export class DatabaseConfigSchema {
  @IsString()
  host: string;

  @IsNumber()
  port: number;

  // Custom condition: SSL required for external hosts
  @IsRequiredIf({
    condition: (obj) => !obj.host.includes('localhost') && !obj.host.includes('127.0.0.1'),
    message: 'SSL configuration is required for external database hosts',
  })
  @IsOptional()
  @IsBoolean()
  ssl?: boolean;

  // Complex condition: connection pooling required for high-traffic environments
  @IsRequiredIf({
    condition: (obj) => {
      const isProduction = obj.environment === 'production';
      const hasHighTraffic = obj.expectedConnections > 100;
      return isProduction || hasHighTraffic;
    },
    message: 'Connection pooling is required for production or high-traffic scenarios',
  })
  @IsOptional()
  @IsObject()
  pooling?: DatabasePoolConfig;
}

Multiple Feature Configurations

@Module({
  imports: [
    FeatureConfigModule.registerMultiple([
      {
        featureName: 'STORAGE',
        schema: StorageConfigSchema,
      },
      {
        featureName: 'EMAIL',
        schema: EmailConfigSchema,
      },
      {
        featureName: 'DATABASE',
        schema: DatabaseConfigSchema,
      },
    ]),
  ],
})
export class AppModule {}

Async Configuration

@Module({
  imports: [
    FeatureConfigModule.registerAsync({
      featureName: 'STORAGE',
      schema: StorageConfigSchema,
      useFactory: async (configService: ConfigService) => {
        // Load configuration from external source
        const externalConfig = await configService.getExternalConfig();
        return {
          provider: externalConfig.storageProvider,
          awsAccessKeyId: externalConfig.aws.accessKeyId,
          // ... other config
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Configuration in Services

@Injectable()
export class EmailService {
  constructor(
    @Inject(generateConfigToken('EMAIL'))
    private readonly emailConfig: EmailConfigSchema,
    
    @Inject(generateConfigToken('STORAGE'))
    private readonly storageConfig: StorageConfigSchema,
  ) {}

  async sendEmailWithAttachment(to: string, subject: string, attachment: string) {
    // Access email configuration
    const emailProvider = this.emailConfig.provider;
    
    // Access storage configuration for attachment handling
    const storageProvider = this.storageConfig.provider;
    
    // TypeScript ensures all required properties are available
    if (emailProvider === 'sendgrid') {
      const apiKey = this.emailConfig.sendgridApiKey; // TypeScript knows this exists
      // Send via SendGrid
    }
  }
}

🧪 Testing

Testing with Custom Environment

describe('StorageService', () => {
  let service: StorageService;

  beforeEach(async () => {
    const mockEnv = {
      STORAGE_PROVIDER: 's3',
      STORAGE_AWS_ACCESS_KEY_ID: 'test-key',
      STORAGE_AWS_SECRET_ACCESS_KEY: 'test-secret',
      STORAGE_S3_BUCKET_NAME: 'test-bucket',
    };

    const module = await Test.createTestingModule({
      imports: [
        FeatureConfigModule.register({
          featureName: 'STORAGE',
          schema: StorageConfigSchema,
          env: mockEnv, // Use custom environment for testing
        }),
      ],
      providers: [StorageService],
    }).compile();

    service = module.get<StorageService>(StorageService);
  });

  it('should configure S3 storage correctly', () => {
    const info = service.getStorageInfo();
    expect(info.provider).toBe('s3');
    expect(info.isS3).toBe(true);
    expect(info.bucketName).toBe('test-bucket');
  });
});

Testing Validation Errors

it('should throw validation error for missing S3 configuration', async () => {
  const invalidEnv = {
    STORAGE_PROVIDER: 's3',
    // Missing required S3 fields
  };

  await expect(
    Test.createTestingModule({
      imports: [
        FeatureConfigModule.register({
          featureName: 'STORAGE',
          schema: StorageConfigSchema,
          env: invalidEnv,
        }),
      ],
    }).compile(),
  ).rejects.toThrow('AWS Access Key is required when using S3 provider');
});

🔧 API Reference

@IsRequiredInEnv(env, envVar?, options?)

🌟 Revolutionary decorator for environment-based validation

Validates that a field is required only when the application is running in a specific environment. This solves the critical problem of having different validation requirements across development, staging, and production environments.

Parameters:

  • env: The environment in which this field is required (e.g., 'production', 'staging')
  • envVar: Environment variable to check (default: 'NODE_ENV')
  • options: Standard class-validator ValidationOptions

Examples:

// Required only in production
@IsRequiredInEnv('production')
apiKey?: string;

// Required in production, check APP_ENV instead of NODE_ENV
@IsRequiredInEnv('production', 'APP_ENV')
databaseUrl?: string;

// Required in staging with custom message
@IsRequiredInEnv('staging', 'NODE_ENV', {
  message: 'Staging API key is required for testing external integrations',
})
stagingApiKey?: string;

@IsRequiredIf(options)

Custom validation decorator for conditional field requirements.

Options:

  • condition: Single function or array of condition functions
  • operator: LogicalOperator.AND or LogicalOperator.OR (default: AND)
  • message: Custom error message

Example:

@IsRequiredIf({
  condition: ConditionHelpers.propertyEquals('provider', 'aws'),
  message: 'AWS credentials required when using AWS provider',
})
apiKey?: string;

ConditionHelpers

Pre-built condition functions for common scenarios:

  • propertyEquals(prop, value): Check if property equals value
  • propertyIn(prop, values): Check if property is in array
  • propertyIsTruthy(prop): Check if property is truthy
  • propertyExists(prop): Check if property exists
  • and(...conditions): Combine conditions with AND
  • or(...conditions): Combine conditions with OR
  • not(condition): Negate a condition

FeatureConfigModule.register(options)

Register a single feature configuration.

Options:

  • featureName: Name of the feature
  • schema: Configuration schema class
  • envPrefix: Custom environment variable prefix
  • freeze: Whether to freeze configuration object
  • env: Custom environment variables object

Environment Variable Mapping

Environment variables are automatically mapped to schema properties:

STORAGE_PROVIDER → provider
STORAGE_AWS_ACCESS_KEY_ID → awsAccessKeyId
STORAGE_S3_BUCKET_NAME → s3BucketName

📝 Best Practices

1. Organize by Features

// ✅ Good: Feature-based configuration
@Module({
  imports: [
    FeatureConfigModule.register({ featureName: 'STORAGE', schema: StorageConfigSchema }),
    FeatureConfigModule.register({ featureName: 'EMAIL', schema: EmailConfigSchema }),
    FeatureConfigModule.register({ featureName: 'AUTH', schema: AuthConfigSchema }),
  ],
})
export class AppModule {}

2. Use Descriptive Validation Messages

// ✅ Good: Clear, actionable error messages
@IsRequiredIf({
  condition: ConditionHelpers.propertyEquals('provider', 'aws'),
  message: 'AWS Access Key ID is required when using AWS provider. Set STORAGE_AWS_ACCESS_KEY_ID environment variable.',
})
awsAccessKeyId?: string;

3. Provide Sensible Defaults

// ✅ Good: Sensible defaults for optional fields
@IsOptional()
@IsNumber()
@Transform(({ value }) => parseInt(value, 10))
maxFileSize?: number = 10485760; // 10MB default

4. Group Related Configurations

// ✅ Good: Group related settings
export class RedisConfigSchema {
  @IsString()
  host: string;

  @IsNumber()
  port: number;

  // Group connection settings
  @IsOptional()
  @IsObject()
  connection?: {
    connectTimeout?: number;
    lazyConnect?: boolean;
    retryDelayOnFailover?: number;
  };
}

🛠️ Configuration Examples

Complete Storage Configuration

export class StorageConfigSchema {
  @IsEnum(StorageProvider)
  provider: StorageProvider;

  @IsOptional()
  @IsString()
  baseDir?: string = '/uploads';

  @IsOptional()
  @IsNumber()
  @Transform(({ value }) => parseInt(value, 10))
  maxFileSize?: number = 10485760;

  // AWS S3 Configuration
  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
  })
  @IsOptional()
  @IsString()
  awsAccessKeyId?: string;

  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
  })
  @IsOptional()
  @IsString()
  awsSecretAccessKey?: string;

  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
  })
  @IsOptional()
  @IsString()
  s3BucketName?: string;

  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.S3),
  })
  @IsOptional()
  @IsString()
  s3Region?: string;

  // Azure Blob Configuration
  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.AZURE),
  })
  @IsOptional()
  @IsString()
  azureAccountName?: string;

  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.AZURE),
  })
  @IsOptional()
  @IsString()
  azureAccountKey?: string;

  @IsRequiredIf({
    condition: ConditionHelpers.propertyEquals('provider', StorageProvider.AZURE),
  })
  @IsOptional()
  @IsString()
  azureContainerName?: string;
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built on top of class-validator and class-transformer
  • Inspired by NestJS's configuration best practices
  • Thanks to the NestJS community for feedback and suggestions

🔗 Links