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
<code>go get github.com/spf13/viper</code>2. Set default values
<code>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"})
}
</code>3. Read a configuration file
<code>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)
}
</code>4. Write a configuration file
<code>import (
"github.com/spf13/viper"
)
func main() {
// ... read config ...
viper.Set("ContentDir", "/path/to/new/content")
if err := viper.WriteConfig(); err != nil {
panic(err)
}
}
</code>5. Watch for configuration changes
<code>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 ...
}
</code>6. Read from environment variables
<code>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)
}
</code>Before running the application, set the environment variables:
<code>export MYAPP_PORT=8080
export MYAPP_HOST=localhost
</code>7. Read from command‑line flags
<code>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"))
}
</code>Run the binary with flags, e.g. ./myapp -port=8081 -host=127.0.0.1 .
8. Read from a remote configuration system
<code>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"))
}
</code>9. Use aliases
<code>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"))
}
</code>Advanced Usage
1. Read a sub‑tree
<code>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)
}
</code>2. Decode into a struct
<code>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)
}
</code>3. Custom decode format with mapstructure
<code>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)
}
</code>4. Multiple Viper instances
<code>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"))
}
</code>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.
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.