Master Go Configuration with Viper: Setup, Usage, and Advanced Tips
This article provides a comprehensive guide to Viper, a powerful Go configuration library, covering its core advantages, step‑by‑step basic usage—including installation, defaults, file handling, environment variables, command‑line flags, remote providers—and advanced techniques such as sub‑tree reading, custom decoding, and managing multiple Viper instances.
Viper is a powerful Go configuration library that offers a complete solution for managing application settings, supporting the 12‑Factor app methodology and handling various configuration formats.
Viper Advantages
Multiple configuration formats : supports JSON, TOML, YAML, HCL, INI, envfile, and Java properties.
Default values : allows setting defaults that are used when a key is missing.
Dynamic updates : watches configuration files and reloads them automatically without restarting.
Environment variable support : reads values from the environment.
Command‑line flag support : binds flags to configuration keys.
Remote configuration : can fetch settings from etcd, Consul, etc., and watch for changes.
Priority mechanism : lets developers define the order of sources.
Alias system : provides alternative names for the same key.
Basic Usage
1. Install Viper
go get github.com/spf13/viper2. Set default values
import (
"github.com/spf13/viper"
)
func main() {
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
}3. Read a configuration file
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config") // name without extension
viper.SetConfigType("yaml") // optional if extension is present
viper.AddConfigPath("/etc/appname/")
viper.AddConfigPath("$HOME/.appname")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
contentDir := viper.GetString("ContentDir")
layoutDir := viper.GetString("LayoutDir")
taxonomies := viper.GetStringMapString("Taxonomies")
fmt.Println(contentDir, layoutDir, taxonomies)
}4. Write a configuration file
import (
"github.com/spf13/viper"
)
func main() {
// ... read config ...
viper.Set("ContentDir", "/path/to/new/content")
if err := viper.WriteConfig(); err != nil {
panic(err)
}
}5. Watch for configuration changes
import (
"fmt"
"github.com/spf13/viper"
"github.com/fsnotify/fsnotify"
)
func main() {
// ... read config ...
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()
// ... application logic ...
}6. Read from environment variables
import (
"fmt"
"os"
"github.com/spf13/viper"
)
func main() {
viper.SetEnvPrefix("myapp")
viper.AutomaticEnv()
port := viper.GetInt("port")
host := viper.GetString("host")
fmt.Println("Port:", port)
fmt.Println("Host:", host)
}Before running the application, set the environment variables:
export MYAPP_PORT=8080
export MYAPP_HOST=localhost7. Read from command‑line flags
import (
"fmt"
"github.com/spf13/viper"
"github.com/spf13/pflag"
)
func main() {
portFlag := pflag.Int("port", 8080, "Application port")
hostFlag := pflag.String("host", "localhost", "Application host")
pflag.Parse()
viper.BindPFlag("port", portFlag)
viper.BindPFlag("host", hostFlag)
fmt.Println("Port:", viper.GetInt("port"))
fmt.Println("Host:", viper.GetString("host"))
}Run the binary with flags, e.g. ./myapp -port=8081 -host=127.0.0.1.
8. Read from a remote configuration system
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/myapp.json")
viper.SetConfigType("json")
if err := viper.ReadRemoteConfig(); err != nil {
panic(err)
}
fmt.Println("Port:", viper.GetInt("port"))
fmt.Println("Host:", viper.GetString("host"))
}9. Use aliases
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.RegisterAlias("loud", "Verbose")
viper.Set("verbose", true) // same as setting "loud"
fmt.Println("Loud:", viper.GetBool("loud"))
fmt.Println("Verbose:", viper.GetBool("Verbose"))
}Advanced Usage
1. Read a sub‑tree
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
// ... read config ...
cacheConfig := viper.Sub("cache")
if cacheConfig == nil {
panic("cache configuration not found")
}
maxItems := cacheConfig.GetInt("max-items")
itemSize := cacheConfig.GetInt("item-size")
fmt.Println("Max items:", maxItems)
fmt.Println("Item size:", itemSize)
}2. Decode into a struct
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
Port int
Host string
Database struct {
User string
Password string
}
}
func main() {
// ... read config ...
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
panic(err)
}
fmt.Println("Port:", cfg.Port)
fmt.Println("Host:", cfg.Host)
fmt.Println("Database user:", cfg.Database.User)
fmt.Println("Database password:", cfg.Database.Password)
}3. Custom decode format with mapstructure
import (
"fmt"
"github.com/spf13/viper"
"github.com/mitchellh/mapstructure"
)
type Config struct {
Servers []string `mapstructure:"servers,omitempty"`
}
func main() {
// ... read config ...
var cfg Config
decoderConfig := mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHook(
mapstructure.StringToSliceHookFunc(","),
),
}
decoder, err := mapstructure.NewDecoder(&decoderConfig)
if err != nil { panic(err) }
if err = decoder.Decode(viper.AllSettings(), &cfg); err != nil { panic(err) }
fmt.Println("Servers:", cfg.Servers)
}4. Multiple Viper instances
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
v1 := viper.New()
v2 := viper.New()
v1.SetConfigName("config1")
v1.AddConfigPath(".")
v1.ReadInConfig()
v2.SetConfigName("config2")
v2.AddConfigPath(".")
v2.ReadInConfig()
fmt.Println("Viper 1:", v1.GetInt("port"), v1.GetString("host"))
fmt.Println("Viper 2:", v2.GetInt("port"), v2.GetString("host"))
}Conclusion
Viper is a robust and easy‑to‑use Go configuration library that simplifies configuration management through defaults, environment variables, command‑line flags, remote providers, and many file formats. Its advanced features—such as aliasing, sub‑tree access, custom decoding, and multiple instances—allow developers to tailor configuration handling to the specific needs of any backend project.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Development Notes
Focused on architecture design, technology trend analysis, and practical development experience sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
