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
- an explicit call to
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.
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.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
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.
Get setup with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>