Config factor from The Twelve-Factor App states that application’s configuration should be isolated from code to make configuring an app for different environments very convenient and scaling it up a breeze.

Configuration management for modern applications which run on so many different environment is becoming complex with advent of microservices and cloud computing. Not to mention dealing with several formats/markup languages to store app’s configurations.

This post is about one of my favorite Go libraries Viper which claims to be a complete configuration solution for Go applications. Among other features, it supports (as of this writing) reading configuration from

  • JSON, TOML, YAML, HCL, and Java properties config files
  • Environment variables
  • Commandline flags
  • Remote config systems (etcd or Consul)

Let’s see how to use Viper to take advantage of some of these features.

Install Viper

go get github.com/spf13/viper

Read from config file

While it supports various file formats, we’ll use an example JSON configuration below.

/config/env.json

{
    "prod": {
        "host": "192.168.1.1",
        "port": "8081",
        "enabled": true
    },
    "dev": {
        "host": "192.168.1.2",
        "port": "8082",
        "enabled": true
    },
    "qa": {
        "host": "192.168.1.3",
        "port": "8083",
        "enabled": true
    }
}

Note: Viper does not require any initialization before using, unless we’ll be dealing multiple different configurations. check working with multiple vipers

Set config file we want to read.

There are 2 ways to do this.
Set path including config file name and extension

viper.SetConfigFile("./configs/env.json")

Or Set path(s) to loook for config files in.


// Add paths. Accepts multiple paths. It will search these paths in given order
viper.AddConfigPath("./configs")
viper.AddConfigPath("$HOME/configs")
// And then register config file name (no extension)
viper.SetConfigName("env")
// Optionally we can set specific config type
// viper.SetConfigType("json")

Read the config file in viper


// Searches for config file in given paths and read it
if err := viper.ReadInConfig(); err != nil {
    log.Fatalf("Error reading config file, %s", err)
}

// Confirm which config file is used
fmt.Printf("Using config: %s\n", viper.ConfigFileUsed())

Get values from viper

Get method can retrieve any value given the key (case-insensitive) to use. Get returns an interface. For a specific value use one of the Get____ methods (e.g. GetInt, GetBool etc)


port := viper.Get("prod.port") // returns string 
//port := viper.GetInt("prod.port") // returns integer
fmt.Printf("Value: %v, Type: %T\n", port, port)

Check if a particular key is set using IsSet


if !viper.IsSet("prod.port") {
    log.Fatal("missing port number")
}

Notice that we can trverse nested configuration using a . delimited path e.g. prod.port

Read values in struct

Instead of reading keys one by one we can extract sub-tree using Sub and decode it in struct using Unmarshal


prod := viper.Sub("prod")

// Unmarshal into struct. Struct fields should match keys from config (case in-sensitive)
type config struct {
    Host    string
    Port    int
    enabled bool
}

var C config

err := prod.Unmarshal(&C)
if err != nil {
    log.Fatalf("unable to decode into struct, %v", err)
}
fmt.Println(C.Host)

Read from Env variables [TODO]


You can find full source of this example in github repo

This post gives just a glimpse of Viper’s capabilities. It’s worth exploring some of the interesting things it offers like,