// Package config loads application configuration from a YAML file + env vars. // Priority (highest → lowest): env vars → config file → defaults. // // Usage: // // cfg, err := config.Load("config/dev.yaml") // fmt.Println(cfg.App.Port) package config import ( "fmt" "github.com/spf13/viper" ) // Config is the root configuration object. Add sub-structs as the app grows. type Config struct { App AppConfig `mapstructure:"app"` Logger LoggerConfig `mapstructure:"logger"` } // AppConfig holds generic application settings. type AppConfig struct { Name string `mapstructure:"name"` Port int `mapstructure:"port"` Env string `mapstructure:"env"` // dev | staging | prod } // LoggerConfig controls logging behavior. type LoggerConfig struct { Level string `mapstructure:"level"` // debug | info | warn | error } // Load reads the YAML file at path and overlays any matching environment // variables (e.g. APP_PORT overrides app.port). func Load(path string) (*Config, error) { v := viper.New() v.SetConfigFile(path) // Allow every config key to be overridden by an env var. // e.g. APP_PORT=9090 overrides app.port v.AutomaticEnv() // Sensible defaults so the binary works without a config file. v.SetDefault("app.name", "go-template") v.SetDefault("app.port", 8080) v.SetDefault("app.env", "dev") v.SetDefault("logger.level", "info") if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("config: reading %q: %w", path, err) } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("config: unmarshalling: %w", err) } return &cfg, nil }