Golang Viper Tutorial

Introduction to golang viper

We typically need to employ multiple settings for development, testing, staging, and production environments when creating and deploying a backend web application. We will learn how to load configurations from files or environment variables using Viper today.

Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed to work within an application and can handle all types of configuration needs and formats. It supports:

  • setting defaults
  • reading from JSON, TOML, YAML, HCL, envfile and Java properties config files
  • live watching and re-reading of config files (optional)
  • reading from environment variables
  • reading from remote config systems (etcd or Consul), and watching changes
  • reading from command line flags
  • reading from buffer
  • setting explicit values

Firstly, you need to install the package:

Advertisement
go get github.com/spf13/viper

 

Using viper to read config from .env file

Wa have an .env file:

PORT=3000
URL="https://test.golinuxcloud.com
USER_NAME="test_user"
PASSWORD="@#!@!#@#%!@#!@#!"

Here is an example of using Viper to read that .env file:

package main

import (
	"fmt"

	"github.com/spf13/viper"
)

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

	fmt.Println("port is: ", viper.Get("PORT"))
	fmt.Println("username is: ", viper.Get("USER_NAME"))
}

Output:

port is:  3000
username is:  test_user

Explanation:

  • Read the .env file by using function: viper.ReadInConfig()
  • 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.

 

Using viper to read config from json file

In this example, we will read content of this json file:

{
    "compilerOptions": {
      "module": "commonjs",
      "target": "es6"
    },
    "exclude": ["node_modules"]
  }

Consider to code below to understand how to work with json file with viper

Advertisement
package main

import (
	"fmt"

	"github.com/spf13/viper"
)

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

	var config ConfigExample

	viper.Unmarshal(&config)

	fmt.Println(config.CompilerOptions.Target)
}

type ConfigExample struct {
	CompilerOptions struct {
		Module string `json:"module"`
		Target string `json:"target"`
	} `json:"compilerOptions"`
	Exclude []string `json:"exclude"`
}

Output:

es6

Explanation:

  • Using viper.AddConfigPath() to let viper know  the location of the config file.
  • Call viper.SetConfigName() function to let viper know the config file name (without extension).
  • viper.SetConfigFile(): the type of config file (.json).
  • Declare a new type ConfigExample struct. This struct will hold all configuration variables of the application that we read from file or environment variables.
  • Read the config by function ReadInConfig() function and parse the value to the ConfigExample struct.

 

Set default value when variable in undefined or empty

Method-1: Without using viper.SetDefault

Using golang viper we can also define a default or fallback variable value in situations when the variable has no value using viper.SetDefault or we can manually also define a default value:

Here I have a sample environment file which I will source and read using viper:

# environment can be test,production,testing
APP_ENV=development
# username
DB_USER=postgres
# secure password
DB_PASS=pass
# app version not set
APP_VERSION=

Here is my sample go code where I will read the defined variables from app.env while for variables where we don't have any value or for any missing variables, we will define a default value.

package main

import (
	"fmt"

	"github.com/spf13/viper"
)

//Function to read an environment or return a default value
func getEnvValue(key string, defaultValue string) string {

	// Get file path
	viper.SetConfigFile("app.env")
	//read configs and handle errors
	err := viper.ReadInConfig()
	if err != nil {
		fmt.Println(err)
	}
	value := viper.GetString(key)
	if value != "" {
		return value
	}
	return defaultValue
}
func main() {
	// reading environments variable using the viper
	appEnv := getEnvValue("APP_ENV", "defaultEnvtesting")
	// not set in our app.env
	appVersion := getEnvValue("APP_VERSION", "1")
	dbPass := getEnvValue("DB_PASS", "1234")
	dbUser := getEnvValue("DB_USER", "goLinux_DB")
	serverAddress := getEnvValue("SERVER_ADDRESS", "127.0.0.1:8080")

	fmt.Printf(" ------%s-----\n", "Reading Environment variables Using Viper package")
	fmt.Printf(" %s = %s \n", "Application_Environment", appEnv)
	fmt.Printf(" %s = %s \n", "Application_Version", appVersion)
	fmt.Printf(" %s = %s \n", "Server_Listening_Address", serverAddress)
	fmt.Printf(" %s = %s \n", "Database_User_Name", dbUser)
	fmt.Printf(" %s = %s \n", "Database_User_Password", dbPass)

}

In the output as you can see, we are printing default value for SERVER_ADDRESS and APP_VERSION as these are not defined in app.env:

# go run main.go 
------Reading Environment variables Using Viper package-----
Application_Environment = development 
Application_Version = 1 
Server_Listening_Address = 127.0.0.1:8080 
Database_User_Name = postgres 
Database_User_Password = pass

 

Method-2: Using viper.SetDefault

In this code I have an YAML based input file which we are reading using viper. Here is the content of creds.yaml file:

---
credentials:
  username: john
  password: secret

This is the same code which reads creds.yaml file and then loads the variables from the file. But in case there are variables which are not defined in out source file or if the values are empty then we will use viper.SetDefault to define the default value for these variables:

package main

import (
	"fmt"

	"github.com/spf13/viper"
)

func userCredentials() {
	fmt.Printf("Connecting to %s at %s\n",
		viper.GetString("webserver.name"),
		viper.GetString("webserver.endpoint"))

	fmt.Printf("Your username is %s\n", viper.GetString("credentials.username"))
	fmt.Printf("Your password is %s\n", viper.GetString("credentials.password"))
}

func main() {
	// Define default values
	viper.SetDefault("webserver.endpoint", "https://127.0.0.1/GET")
	viper.SetDefault("webserver.name", "Testing Endpoint")

	viper.AddConfigPath(".")
	viper.SetConfigName("creds")
	if err := viper.ReadInConfig(); err != nil {
		panic(err)
	}

	userCredentials()
}

Output:

# go run main.go 
Connecting to Testing Endpoint at https://127.0.0.1/GET
Your username is john
Your password is secret

As you can see, we had not defined any value for webserver name and webserver endpoint in creds.yaml file so viper has assigned the default value from the code.

 

Summary

In today's post, I have given 2 examples of reading config from .env or json file using viper in Golang. Some advantages of viper package:

  • It can find, load, and unmarshal values from a config file.
  • It supports many types of files, such as JSON, TOML, YAML, ENV, or INI.
  • It can also read values from environment variables or command-line flags.
  • It gives us the ability to set or override default values.

 

References

https://github.com/spf13/viper

Advertisement

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X