Subha Chanda Subha is a web developer who is passionate about learning and experimenting with new things.

Handling Go configuration with Viper

4 min read 1224

Go Logo Over a Grayish Background

Go has many packages that can handle configurations for your applications. But Viper is one of the most popular and complete configuration solutions that helps you configure your application easily.

What is Viper?

Viper makes your app compliant with the Twelve-Factor-App checklist, which is a method for building SaaS applications. First drafted by Heroku, this checklist helps make your apps more portable and resilient by using declarative formats to set up automation.

This helps new developers in the codebase get started easily and quickly, and it also minimizes the difference between production and development — all of which helps in continuous deployment.

Viper currently supports:

  • Setting defaults for applications
  • Reading from JSON, TOML, YAML, HCL, .env files, and Java properties config files
  • Reading from environment variables — it has a precedence order in which it reads from the configuration file:
    • an explicit call to Set
    • flag
    • .env files
    • config
    • key/value store
    • default

Why use Viper?

Ideally, Viper enables us to spend less time configuring the application and more time actually building our app. Viper achieves this by ensuring we can set up our configuration easily.

There are many cases when we need to set up defaults for the application or load config variables from different file types, and this is where using Viper can be extremely useful. It can even live-read config variables, work with flags, and enable you to marshal and unmarshal.

The use cases for Viper are enormous, and building applications with Go becomes more manageable when used with Viper. In this article, you’ll learn how to use Viper and apply it to these use cases.

Installing Viper

Installing Viper is similar to installing any package in Go. The first step is to initialize the Go mod file. The best way to do this is to initialize the folder with git init.

Next, set up the git origin using git remote add origin ORIGIN_URL, then initialize the project with go mod init. It’ll create a go.mod file.

The go.mod file is all you need to install any packages to your project. To install Viper, run the following command in your terminal:



go get github.com/spf13/viper

Using Viper with Go

Reading from .env files

Let’s load environment variables from a .env file. Create the .env file in your project’s root directory and define your environment variables there. For example, let’s say you have three variables that store the database address, port, and an API secret. The file will look similar to the file shown below:

PORT=3000
MONGO_URI=mongodb+srv://nemo0:[email protected]/myFirstDatabase?retryWrites=true&w=majority
API_SECRET=Y1VFGSea2EPa6v3gFY84swUJbZCHY8WEzKfdq6uPUyRjYR

To use these .env variables in your main.go file, you’ll have to let Viper know the path and file name of your .env file. If your Go file and the .env file are at the exact location, you can initialize the .env file with Viper using this:

viper.SetConfigFile(".env")

To read the .env file, you have to add the following code:

viper.ReadInConfig()

Once you add the above line after setting the config file, you can read the environment values with viper.Get("KEY"), where KEY is the variable name. For example, viper.Get("PORT") will read the PORT from the .env file.

The complete code for the above example will look like this:

package main

import (
    "fmt"

    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigFile(".env")
    viper.ReadInConfig()

    fmt.Println(viper.Get("PORT"))
}

Running this code will output 3000 in the console.

But, as we know, Viper is not limited only to .env files only. Let’s take our app one step further and read the configuration from a JSON file.

Reading values from a JSON file with Viper

Let’s say you have a config.json file inside a configs folder in your project root directory. To read data from this file, you can write:

    viper.AddConfigPath("./configs")
    viper.SetConfigName("config") // Register config file name (no extension)
    viper.SetConfigType("json")   // Look for specific type
    viper.ReadInConfig()

    port := viper.Get("prod.port")

    fmt.Println(port)

The viper.AddConfigPath("./configs") line sets the config file path. You can specify multiple paths by adding multiple AddConfigPath methods, and Viper will read them in the order you provide.

viper.SetConfigName("config") registers the configuration file. In most cases, you don’t add the extension here. viper.SetConfigType("json") will tell Viper to read-only from JSON files, and it is an optional field. Finally, after adding viper.ReadInConfig(), you are ready to read from the configuration file.

If you have a configuration JSON file like below, you can read the port variable using viper.Get("prod.port"), as seen in the above code example.

{
  "prod": {
    "port": "3000"
  }
}

Using WatchConfig() in Viper

Viper gives you the ability to live-read the changes from your config file via the WatchConfig() method. Here’s a simple example of this implemented with the fsnotify package, a cross-platform system notifications package.

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("Config file changed:", e.Name)
})

The WatchConfig() method will look for changes in the config file, while the onConfigChange is an optional method that will run each time the file changes.

Working with flags in Viper

There may be cases when you don’t want to type sensitive information repeatedly in the CLI. Viper can help! You can save the sensitive information in your .env or any other config file, read it from there, and pass it to the CLI.

There’s a package called Cobra that enables us to create powerful CLI applications and helps us set flag values.


More great articles from LogRocket:


Here’s an example:

package main

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

func main() {
    command := &cobra.Command{
        Run: func(c *cobra.Command, args []string) {
            fmt.Println(viper.GetString("Flag"))
        },
    }

    viper.SetDefault("Flag", "Flag Value")

    command.Execute()
}

Here, we use two packages apart from using fmt. The first package is cobra, which creates a CLI command, and the second package, viper, sets the default value.

The command variable in the above example holds a fundamental function that runs on the CLI and prints the flag value. A default value for the flag is set using viper.SetDefault("Flag", "Flag Value").

We run the CLI app using the command.Execute() method. Running this file will output Flag Value in the terminal because it is set as the default value for the flag. You can also read from separate config files and bind the values to a flag.

Marshaling and unmarshaling with Viper

Viper can help you with marshaling and unmarshaling values. Marshaling is the process of converting a string into JSON, and unmarshaling is the opposite. Viper gives two methods, Marshal and Unmarshal, to accomplish the tasks.

Conclusion

In this article, we’ve discussed the use cases of using Viper with Go. You’ve learned how to install and set up Viper, and we covered common use cases, such as loading environment variables from different file types. We’ve also covered how to use the WatchConfig method and use flags in Go.

Viper has many more use cases than what I’ve mentioned in this article. You can visit the GitHub repo to get an idea of all the features Viper provides. Using Viper can help you set up your application in a more robust way.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Subha Chanda Subha is a web developer who is passionate about learning and experimenting with new things.

Leave a Reply