⚙️ Dynamic configuration management for HSuite applications
Advanced configuration management module providing environment-based settings, secure credential management, and dynamic configuration updates for distributed blockchain applications.
Table of Contents
Quick Start
Installation
Copy npm install @hsuite/smart-config
Basic Setup
Copy import { SmartConfigModule } from '@hsuite/smart-config';
@Module({
imports: [
SmartConfigModule.forRoot({
envFilePath: ['.env.local', '.env'],
isGlobal: true,
expandVariables: true
})
]
})
export class AppModule {}
Basic Usage
Copy @Injectable()
export class ConfigService {
constructor(private smartConfig: SmartConfigService) {}
getApiKey(): string {
return this.smartConfig.get('API_KEY');
}
getDatabaseConfig(): DatabaseConfig {
return this.smartConfig.getDatabaseConfig();
}
}
Architecture
Core Components
⚙️ Configuration Management
SmartConfigService
- Core configuration service with type safety
Environment Loading - Multi-environment configuration support
Dynamic Updates - Runtime configuration updates
🔒 Security Features
Credential Encryption - Automatic encryption for sensitive values
Secret Management - Secure handling of API keys and passwords
Environment Isolation - Separate configs for different environments
🔄 Dynamic Configuration
Hot Reloading - Configuration updates without restart
Validation - Schema-based configuration validation
Type Safety - Full TypeScript support for configuration objects
Module Structure
Copy src/
├── interfaces/ # Configuration interfaces
├── services/ # Configuration services
├── decorators/ # Configuration decorators
├── validators/ # Configuration validators
├── smart-config.service.ts # Core configuration service
├── smart-config.module.ts # Module configuration
└── index.ts # Public API exports
API Reference
SmartConfigModule
Static Methods
forRoot(options: SmartConfigOptions): DynamicModule
Configures the smart config module with static options.
Copy interface SmartConfigOptions {
envFilePath?: string | string[];
isGlobal?: boolean;
expandVariables?: boolean;
validationSchema?: any;
cache?: boolean;
}
forRootAsync(options: SmartConfigAsyncOptions): DynamicModule
Configures the smart config module with async dependency injection.
SmartConfigService
Core Methods
get<T = any>(key: string, defaultValue?: T): T
Retrieves configuration value by key
Parameters : key
- Configuration key, defaultValue
- Fallback value
Returns : Configuration value or default
getOrThrow<T = any>(key: string): T
Gets configuration value or throws error if not found
Parameters : key
- Configuration key
Throws : Error
if key not found
getDatabaseConfig(): DatabaseConfig
Returns typed database configuration
Returns : Complete database configuration object
getAuthConfig(): AuthConfig
Returns authentication configuration
Returns : Authentication settings with type safety
getNetworkConfig(): NetworkConfig
Returns network and blockchain configuration
Returns : Network configuration for all supported chains
Guides
Environment Configuration Guide
Learn how to set up environment-specific configurations and secrets. Configure validation schemas, environment variable management, and secure credential handling.
Dynamic Updates Guide
Implement runtime configuration updates and hot reloading. Set up configuration watchers, event-driven updates, and real-time configuration management.
Validation Guide
Set up configuration validation schemas and type safety. Implement Joi validation, custom validators, and configuration error handling.
Examples
Complete Module Configuration
Copy import { SmartConfigModule } from '@hsuite/smart-config';
import * as Joi from 'joi';
@Module({
imports: [
SmartConfigModule.forRoot({
envFilePath: ['.env.local', '.env', '.env.production'],
isGlobal: true,
expandVariables: true,
cache: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required().min(32),
HEDERA_ACCOUNT_ID: Joi.string().pattern(/^0\.0\.\d+$/),
HEDERA_PRIVATE_KEY: Joi.string().required(),
API_RATE_LIMIT: Joi.number().default(100),
ENABLE_CORS: Joi.boolean().default(true)
})
})
]
})
export class AppModule {}
Environment-Specific Configuration
Copy import { Injectable } from '@nestjs/common';
import { SmartConfigService } from '@hsuite/smart-config';
@Injectable()
export class EnvironmentConfigService {
constructor(private config: SmartConfigService) {}
// Database Configuration
getDatabaseConfig() {
return {
type: 'postgres',
host: this.config.get('DB_HOST', 'localhost'),
port: this.config.get('DB_PORT', 5432),
username: this.config.get('DB_USERNAME'),
password: this.config.get('DB_PASSWORD'),
database: this.config.get('DB_DATABASE'),
ssl: this.config.get('DB_SSL', false),
synchronize: this.config.get('DB_SYNCHRONIZE', false),
logging: this.config.get('DB_LOGGING', false),
entities: ['dist/**/*.entity{.ts,.js}'],
migrations: ['dist/migrations/*{.ts,.js}']
};
}
// JWT Configuration
getJwtConfig() {
return {
secret: this.config.getOrThrow('JWT_SECRET'),
signOptions: {
expiresIn: this.config.get('JWT_EXPIRES_IN', '24h'),
issuer: this.config.get('JWT_ISSUER', 'hsuite'),
audience: this.config.get('JWT_AUDIENCE', 'hsuite-users')
},
refreshTokenExpiresIn: this.config.get('JWT_REFRESH_EXPIRES_IN', '7d')
};
}
// Hedera Configuration
getHederaConfig() {
return {
network: this.config.get('HEDERA_NETWORK', 'testnet'),
accountId: this.config.getOrThrow('HEDERA_ACCOUNT_ID'),
privateKey: this.config.getOrThrow('HEDERA_PRIVATE_KEY'),
publicKey: this.config.get('HEDERA_PUBLIC_KEY'),
mirrorNodeUrl: this.config.get(
'HEDERA_MIRROR_NODE_URL',
'https://testnet.mirrornode.hedera.com'
),
consensusNodeUrl: this.config.get(
'HEDERA_CONSENSUS_NODE_URL',
'https://testnet.hedera.com'
)
};
}
// Redis Configuration
getRedisConfig() {
return {
host: this.config.get('REDIS_HOST', 'localhost'),
port: this.config.get('REDIS_PORT', 6379),
password: this.config.get('REDIS_PASSWORD'),
db: this.config.get('REDIS_DB', 0),
keyPrefix: this.config.get('REDIS_KEY_PREFIX', 'hsuite:'),
ttl: this.config.get('REDIS_TTL', 3600),
maxRetriesPerRequest: this.config.get('REDIS_MAX_RETRIES', 3)
};
}
// API Configuration
getApiConfig() {
return {
port: this.config.get('PORT', 3000),
host: this.config.get('HOST', '0.0.0.0'),
globalPrefix: this.config.get('API_PREFIX', 'api'),
version: this.config.get('API_VERSION', 'v1'),
documentation: {
enabled: this.config.get('API_DOCS_ENABLED', true),
path: this.config.get('API_DOCS_PATH', 'docs')
},
rateLimit: {
windowMs: this.config.get('RATE_LIMIT_WINDOW', 15 * 60 * 1000), // 15 minutes
max: this.config.get('RATE_LIMIT_MAX', 100),
message: 'Too many requests from this IP'
},
cors: {
enabled: this.config.get('ENABLE_CORS', true),
origin: this.config.get('CORS_ORIGIN', '*'),
credentials: this.config.get('CORS_CREDENTIALS', true)
}
};
}
// Mail Configuration
getMailConfig() {
return {
transport: {
host: this.config.get('SMTP_HOST'),
port: this.config.get('SMTP_PORT', 587),
secure: this.config.get('SMTP_SECURE', false),
auth: {
user: this.config.get('SMTP_USER'),
pass: this.config.get('SMTP_PASS')
}
},
defaults: {
from: this.config.get('SMTP_FROM', '[email protected] ')
},
template: {
dir: this.config.get('MAIL_TEMPLATE_DIR', './templates'),
adapter: 'handlebars',
options: {
strict: true
}
}
};
}
}
Dynamic Configuration Updates
Copy import { Injectable, OnModuleInit } from '@nestjs/common';
import { SmartConfigService } from '@hsuite/smart-config';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class DynamicConfigService implements OnModuleInit {
private configCache = new Map<string, any>();
constructor(
private config: SmartConfigService,
private eventEmitter: EventEmitter2
) {}
onModuleInit() {
this.setupConfigWatchers();
}
async updateConfiguration(key: string, value: any) {
// Validate new configuration value
await this.validateConfigUpdate(key, value);
// Update in cache
this.configCache.set(key, value);
// Emit configuration change event
this.eventEmitter.emit('config.updated', { key, value, timestamp: new Date() });
// Persist to external config store if needed
await this.persistConfigChange(key, value);
return {
success: true,
key,
newValue: value,
updatedAt: new Date()
};
}
getConfigWithFallback<T>(key: string, fallback: T): T {
// Check cache first
if (this.configCache.has(key)) {
return this.configCache.get(key);
}
// Fall back to environment variables
const envValue = this.config.get(key);
if (envValue !== undefined) {
this.configCache.set(key, envValue);
return envValue;
}
// Use provided fallback
this.configCache.set(key, fallback);
return fallback;
}
async reloadConfiguration() {
try {
// Clear cache
this.configCache.clear();
// Reload environment variables
await this.config.reload();
// Emit reload event
this.eventEmitter.emit('config.reloaded', { timestamp: new Date() });
return {
success: true,
message: 'Configuration reloaded successfully',
reloadedAt: new Date()
};
} catch (error) {
throw new Error(`Failed to reload configuration: ${error.message}`);
}
}
getFeatureFlags() {
return {
enableNewUI: this.getConfigWithFallback('FEATURE_NEW_UI', false),
enableBetaFeatures: this.getConfigWithFallback('FEATURE_BETA', false),
enableAnalytics: this.getConfigWithFallback('FEATURE_ANALYTICS', true),
enableCaching: this.getConfigWithFallback('FEATURE_CACHING', true),
maintenanceMode: this.getConfigWithFallback('MAINTENANCE_MODE', false)
};
}
private setupConfigWatchers() {
// Watch for configuration file changes
if (this.config.get('ENABLE_CONFIG_WATCHING', false)) {
// Implementation for file system watching
console.log('Configuration watching enabled');
}
}
private async validateConfigUpdate(key: string, value: any) {
// Define validation rules for different config keys
const validationRules = {
'API_RATE_LIMIT': (val: any) => Number.isInteger(val) && val > 0,
'JWT_EXPIRES_IN': (val: any) => typeof val === 'string' && /^\d+[smhd]$/.test(val),
'HEDERA_ACCOUNT_ID': (val: any) => typeof val === 'string' && /^0\.0\.\d+$/.test(val)
};
const validator = validationRules[key];
if (validator && !validator(value)) {
throw new Error(`Invalid value for configuration key: ${key}`);
}
}
private async persistConfigChange(key: string, value: any) {
// Implementation for persisting config changes
// Could be database, external config service, etc.
console.log(`Persisting config change: ${key} = ${value}`);
}
}
Configuration Validation Service
Copy import { Injectable } from '@nestjs/common';
import { SmartConfigService } from '@hsuite/smart-config';
import * as Joi from 'joi';
@Injectable()
export class ConfigValidationService {
constructor(private config: SmartConfigService) {}
validateAllConfigurations(): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate required environment variables
const requiredConfigs = [
'JWT_SECRET',
'DATABASE_URL',
'HEDERA_ACCOUNT_ID',
'HEDERA_PRIVATE_KEY'
];
for (const configKey of requiredConfigs) {
if (!this.config.get(configKey)) {
errors.push(`Missing required configuration: ${configKey}`);
}
}
// Validate configuration formats
this.validateJwtSecret(errors);
this.validateDatabaseUrl(errors);
this.validateHederaConfig(errors, warnings);
this.validateApiConfig(warnings);
return {
isValid: errors.length === 0,
errors,
warnings,
validatedAt: new Date()
};
}
private validateJwtSecret(errors: string[]) {
const jwtSecret = this.config.get('JWT_SECRET');
if (jwtSecret && jwtSecret.length < 32) {
errors.push('JWT_SECRET must be at least 32 characters long');
}
}
private validateDatabaseUrl(errors: string[]) {
const dbUrl = this.config.get('DATABASE_URL');
if (dbUrl && !dbUrl.startsWith('postgresql://')) {
errors.push('DATABASE_URL must be a valid PostgreSQL connection string');
}
}
private validateHederaConfig(errors: string[], warnings: string[]) {
const accountId = this.config.get('HEDERA_ACCOUNT_ID');
if (accountId && !/^0\.0\.\d+$/.test(accountId)) {
errors.push('HEDERA_ACCOUNT_ID must be in format 0.0.xxx');
}
const network = this.config.get('HEDERA_NETWORK', 'testnet');
if (network === 'mainnet') {
warnings.push('Using Hedera mainnet - ensure this is intentional');
}
}
private validateApiConfig(warnings: string[]) {
const port = this.config.get('PORT', 3000);
if (port < 1024) {
warnings.push('API port is below 1024 - may require elevated privileges');
}
if (this.config.get('ENABLE_CORS', true) && this.config.get('CORS_ORIGIN') === '*') {
warnings.push('CORS is enabled for all origins - consider restricting in production');
}
}
}
interface ValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
validatedAt: Date;
}
Integration
Required Dependencies
Copy {
"@nestjs/config": "^3.0.0",
"joi": "^17.0.0",
"dotenv": "^16.0.0"
}
Environment File Structure
Copy # .env.local (development)
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/hsuite_dev
DB_SYNCHRONIZE=true
DB_LOGGING=true
# JWT
JWT_SECRET=your-super-secret-jwt-key-minimum-32-characters
JWT_EXPIRES_IN=24h
# Hedera Testnet
HEDERA_NETWORK=testnet
HEDERA_ACCOUNT_ID=0.0.123456
HEDERA_PRIVATE_KEY=302e020100300506032b657004220420...
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# Feature Flags
FEATURE_NEW_UI=true
FEATURE_BETA=true
ENABLE_CONFIG_WATCHING=true
Production Environment
Copy # .env.production
NODE_ENV=production
PORT=8080
LOG_LEVEL=info
# Database
DATABASE_URL=postgresql://user:securepass@prod-db:5432/hsuite
DB_SYNCHRONIZE=false
DB_LOGGING=false
# JWT
JWT_SECRET=production-jwt-secret-very-long-and-secure
JWT_EXPIRES_IN=1h
# Hedera Mainnet
HEDERA_NETWORK=mainnet
HEDERA_ACCOUNT_ID=0.0.987654
HEDERA_PRIVATE_KEY=encrypted_private_key_here
# Redis
REDIS_HOST=redis-cluster
REDIS_PORT=6379
REDIS_PASSWORD=secure_redis_password
# Security
ENABLE_CORS=false
API_RATE_LIMIT=50
⚙️ Type Safety : Full TypeScript support with configuration interfaces and validation.
🔒 Security : Encrypted credential storage and environment-based configuration isolation.
🔄 Dynamic : Runtime configuration updates with validation and event emission.
Built with ❤️ by the HbarSuite Team
Copyright © 2024 HbarSuite. All rights reserved.