# Environment-Based Configuration System This package provides a configuration system for the Tender Management backend that loads configuration from both OS environment variables and `.env` files, with **OS environment variables taking priority** over `.env` file values. It maintains the same structure as the previous YAML-based system while using environment variables for all configuration values. ## Priority System The configuration loader follows this priority order: 1. **OS Environment Variables** (highest priority) - System-level environment variables 2. **`.env` File Values** (lower priority) - File-based configuration for development This means that if you set `SERVER_PORT=8080` in your `.env` file but have `SERVER_PORT=9000` as an OS environment variable, the system will use `9000`. ## Architecture ### Base Configuration (`BaseConfig`) The configuration system provides common configuration structs that are used across all commands: - **ServerConfig**: HTTP server configuration (host, port, timeouts) - **DatabaseConfig**: MongoDB connection configuration - **CacheConfig**: Redis connection configuration - **LoggingConfig**: Logging system configuration - **RateLimitConfig**: Rate limiting configuration ### Command-Specific Configuration Each command can extend the base configuration by embedding the common configs and adding their own specific configuration fields. ## Usage ### 1. Create Command-Specific Config Create a config file for your command (e.g., `cmd/yourcommand/config.go`): ```go package main import ( "time" "tm/pkg/config" ) // Config holds configuration for your command type Config struct { config.ServerConfig config.DatabaseConfig config.CacheConfig config.LoggingConfig YourSpecific YourSpecificConfig } type YourSpecificConfig struct { SomeField string `env:"SOME_FIELD"` SomeTimeout time.Duration `env:"SOME_TIMEOUT"` SomeNumber int `env:"SOME_NUMBER"` } ``` ### 2. Load Configuration In your command's `main.go` or bootstrap file: ```go func initConfig() Config { conf, err := config.LoadConfig(".", &Config{}) if err != nil { panic(fmt.Sprintf("Failed to load config: %v", err)) } return *conf } ``` ### 3. Configuration Sources #### Option A: Using .env File Only (Development) Create a `.env` file in your command directory: ```env # Server Configuration SERVER_HOST=localhost SERVER_PORT=8080 SERVER_TIMEOUT=30s SERVER_READ_TIMEOUT=10s SERVER_WRITE_TIMEOUT=10s # Database Configuration MONGODB_URI=mongodb://localhost:27017 MONGODB_NAME=your_database MONGODB_TIMEOUT=10s MONGODB_MAX_POOL_SIZE=100 # Cache Configuration REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 REDIS_POOL_SIZE=10 # Logging Configuration LOG_LEVEL=info LOG_FORMAT=json LOG_OUTPUT=stdout LOG_FILE_PATH=/var/log/tm/your-command.log LOG_FILE_MAX_SIZE=100 LOG_FILE_MAX_BACKUPS=5 LOG_FILE_MAX_AGE=30 LOG_FILE_COMPRESS=true # Your command-specific configuration SOME_FIELD=value SOME_TIMEOUT=30s SOME_NUMBER=42 ``` #### Option B: Using OS Environment Variables (Production) ```bash # Set environment variables directly export SERVER_HOST=0.0.0.0 export SERVER_PORT=8080 export MONGODB_URI=mongodb://mongo:27017 export MONGODB_NAME=production_db export LOG_LEVEL=error # Run your application ./your-command ``` #### Option C: Mixed Approach (Recommended) 1. Create a `.env` file with development defaults 2. Override specific values with OS environment variables for production: ```bash # .env file contains development defaults # Override critical values with OS env vars export MONGODB_URI=mongodb://production-server:27017 export MONGODB_NAME=production_db export LOG_LEVEL=error # Run application - will use .env defaults except for overridden values ./your-command ``` ## Priority Example Given this `.env` file: ```env SERVER_HOST=localhost SERVER_PORT=8080 LOG_LEVEL=debug ``` And these OS environment variables: ```bash export SERVER_PORT=9000 export LOG_LEVEL=error ``` The final configuration will be: - `SERVER_HOST=localhost` (from .env file, no OS override) - `SERVER_PORT=9000` (OS environment variable overrides .env) - `LOG_LEVEL=error` (OS environment variable overrides .env) ## Advanced Features ### Checking Configuration Source You can check whether a configuration value came from OS environment or .env file: ```go import "tm/pkg/config" func main() { value, isFromOS := config.GetEnvWithPriority("SERVER_PORT") if isFromOS { fmt.Printf("SERVER_PORT=%s (from OS environment)\n", value) } else { fmt.Printf("SERVER_PORT=%s (from .env file)\n", value) } } ``` ### Graceful .env File Handling The system gracefully handles missing `.env` files: - If `.env` file exists: loads values as defaults - If `.env` file is missing: continues with OS environment variables only - No errors are thrown for missing `.env` files ## Examples ### Web Command Config ```go type Config struct { config.ServerConfig config.DatabaseConfig config.CacheConfig config.LoggingConfig config.RateLimitConfig UserAuth AuthConfig CustomerAuth AuthConfig Assets AssetsConfig } type AuthConfig struct { JWT JWTConfig } type JWTConfig struct { AccessSecret string `env:"USER_AUTH_ACCESS_SECRET"` RefreshSecret string `env:"USER_AUTH_REFRESH_SECRET"` AccessExpiresIn int `env:"USER_AUTH_ACCESS_EXPIRES_IN"` RefreshExpiresIn int `env:"USER_AUTH_REFRESH_EXPIRES_IN"` } type AssetsConfig struct { FlagsPath string `env:"ASSETS_FLAGS_PATH"` } ``` ### Scraper Command Config ```go type Config struct { config.DatabaseConfig config.LoggingConfig TED ScraperConfig } type ScraperConfig struct { BaseURL string `env:"TED_BASE_URL"` Timeout time.Duration `env:"TED_TIMEOUT"` MaxRetries int `env:"TED_MAX_RETRIES"` RetryDelay time.Duration `env:"TED_RETRY_DELAY"` UserAgent string `env:"TED_USER_AGENT"` MaxConcurrency int `env:"TED_MAX_CONCURRENCY"` DownloadDir string `env:"TED_DOWNLOAD_DIR"` CleanupAfter time.Duration `env:"TED_CLEANUP_AFTER"` ScrapingInterval time.Duration `env:"TED_SCRAPING_INTERVAL"` } ``` ## Features ### Type Safety The configuration loader uses Go generics to provide type-safe configuration loading. The function signature ensures that you get back exactly the type you expect. ### Environment Variable Priority The configuration system loads values with explicit priority: 1. OS environment variables (system-level, highest priority) 2. .env file values (file-based, development defaults) ### Reflection-Based Parsing The system uses reflection to automatically parse environment variables into struct fields based on the `env` tags. ### Supported Types The configuration system supports the following Go types: - `string` - `int`, `int8`, `int16`, `int32`, `int64` - `bool` - `float32`, `float64` - `time.Duration` (parsed from strings like "30s", "1h", "5m") ## Best Practices 1. **Use descriptive environment variable names**: Choose clear, descriptive names for your environment variables (e.g., `MONGODB_URI` instead of `DB_URI`). 2. **Use consistent naming conventions**: Use uppercase with underscores for environment variable names (e.g., `SERVER_HOST`, `LOG_LEVEL`). 3. **Group related variables**: Use prefixes to group related configuration (e.g., `MONGODB_*`, `REDIS_*`, `LOG_*`). 4. **Document your configuration**: Add comments in your `.env` files explaining what each variable does. 5. **Use appropriate types**: Use `time.Duration` for durations, proper numeric types for numbers, etc. 6. **Provide sensible defaults in .env**: Use `.env` files to provide development defaults and override with OS environment variables for production. 7. **Leverage the priority system**: Use `.env` for development defaults and OS environment variables for production overrides. 8. **Keep secrets in OS environment variables**: Never put sensitive values like API keys or passwords in `.env` files; use OS environment variables instead. ## Migration from YAML System If you're migrating from the old YAML-based configuration system: 1. Remove `mapstructure` tags from your config structs 2. Add `env` tags to your config struct fields 3. Create `.env` files instead of `config.yaml` files 4. Update your `LoadConfig` calls to use the new system 5. Update function signatures to use the new config types Example migration: ```go // Old way (YAML) type Config struct { Server config.ServerConfig `mapstructure:"server"` Database config.DatabaseConfig `mapstructure:"database"` } // New way (.env) type Config struct { Server config.ServerConfig Database config.DatabaseConfig } // Old way (YAML loading) conf, err := config.LoadConfig(".", &Config{}) // New way (.env loading) - same API! conf, err := config.LoadConfig(".", &Config{}) ``` ## Environment Variable Naming Convention The system follows these naming conventions: - **Server**: `SERVER_*` (e.g., `SERVER_HOST`, `SERVER_PORT`) - **Database**: `MONGODB_*` (e.g., `MONGODB_URI`, `MONGODB_NAME`) - **Cache**: `REDIS_*` (e.g., `REDIS_HOST`, `REDIS_PORT`) - **Logging**: `LOG_*` (e.g., `LOG_LEVEL`, `LOG_FORMAT`) - **Rate Limiting**: `RATE_LIMIT_*` (e.g., `RATE_LIMIT_REQUESTS_PER_MINUTE`) ## Error Handling The configuration system provides detailed error messages when: - The `.env` file exists but cannot be loaded - Environment variables cannot be parsed into the expected types - Required fields are missing (handled by your application logic) Note: Missing `.env` files do not cause errors - the system will continue with OS environment variables only. ## Security Considerations 1. **Never commit `.env` files with secrets**: Add `.env` to your `.gitignore` file and never include sensitive values 2. **Use OS environment variables for production secrets**: Set sensitive values like API keys and database passwords as OS environment variables 3. **Use environment-specific files**: Create different `.env` files for different environments (`.env.local`, `.env.development`, etc.) 4. **Validate sensitive values**: Always validate sensitive configuration values like API keys and database URIs 5. **Use secure defaults**: Provide secure default values for security-related configuration 6. **Leverage priority system for security**: Keep development defaults in `.env` and override with secure OS environment variables in production ## Deployment Strategies ### Development ```bash # Use .env file for all configuration ./your-command ``` ### Staging ```bash # Use .env for defaults, override key values export MONGODB_URI=mongodb://staging-db:27017 export LOG_LEVEL=warn ./your-command ``` ### Production ```bash # Override all critical values with OS environment variables export MONGODB_URI=mongodb://prod-cluster:27017 export MONGODB_NAME=production export LOG_LEVEL=error export SERVER_HOST=0.0.0.0 ./your-command ``` ### Docker ```dockerfile # Set production values in Dockerfile or docker-compose ENV MONGODB_URI=mongodb://mongo:27017 ENV LOG_LEVEL=info ENV SERVER_HOST=0.0.0.0 ```