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.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
Master Go Configuration with Viper: Setup, Usage, and Advanced Tips

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/viper

2. 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=localhost

7. 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backend-developmentGoConfiguration ManagementViper
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.