Backend Development 10 min read

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

<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(&amp;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(&amp;decoderConfig)
    if err != nil { panic(err) }
    if err = decoder.Decode(viper.AllSettings(), &amp;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.

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

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