Configuration with Viper in Go

Configuration with Viper in Go

Hi, I am Amit Shekhar, Co-Founder @ Outcome School • IIT 2010-14 • I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

In this blog, we will learn how to load the configurations from the envfile in Golang using Viper in Go (Golang) projects.

This article was originally published at Outcome School.

About the viper package: Viper is used to find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile, or Java properties formats.

So, let's start learning to use this viper package to load configuration from the envfile in Go.

I will be using the below-mentioned project for the implementation part. The project follows a clean architecture in Go Language. You can find the complete code for loading the configuration using the Viper package mentioned in the blog in the project itself.

Link to the project: Go Backend Clean Architecture.

As a first step, we should install the package:

go get github.com/spf13/viper

We have a .env file in our project as below:

APP_ENV=development
SERVER_ADDRESS=:8080
PORT=8080
CONTEXT_TIMEOUT=2
DB_HOST=localhost
DB_PORT=27017
DB_USER=
DB_PASS=
DB_NAME=go-backend-clean-architecture-db
ACCESS_TOKEN_EXPIRY_HOUR = 2
REFRESH_TOKEN_EXPIRY_HOUR = 168
ACCESS_TOKEN_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret

And, we want to load the configuration from this .env file.

Now, we have to create a struct Env in the env.go file as below:

type Env struct {
    AppEnv                 string `mapstructure:"APP_ENV"`
    ServerAddress          string `mapstructure:"SERVER_ADDRESS"`
    ContextTimeout         int    `mapstructure:"CONTEXT_TIMEOUT"`
    DBHost                 string `mapstructure:"DB_HOST"`
    DBPort                 string `mapstructure:"DB_PORT"`
    DBUser                 string `mapstructure:"DB_USER"`
    DBPass                 string `mapstructure:"DB_PASS"`
    DBName                 string `mapstructure:"DB_NAME"`
    AccessTokenExpiryHour  int    `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR"`
    RefreshTokenExpiryHour int    `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"`
    AccessTokenSecret      string `mapstructure:"ACCESS_TOKEN_SECRET"`
    RefreshTokenSecret     string `mapstructure:"REFRESH_TOKEN_SECRET"`
}

Then, create a function in the same env.go file to load the .env file and unmarshal it into the above created Env struct.

func NewEnv() *Env {
    env := Env{}
    viper.SetConfigFile(".env")

    err := viper.ReadInConfig()
    if err != nil {
        log.Fatal("Can't find the file .env : ", err)
    }

    err = viper.Unmarshal(&env)
    if err != nil {
        log.Fatal("Environment can't be loaded: ", err)
    }

    if env.AppEnv == "development" {
        log.Println("The App is running in development env")
    }

    return &env
}

Here, we can notice that we have provided the file .env to the viper using the SetConfigFile method, then read and finally unmarshal it into the Env struct.

The complete env.go file will be as below:

package bootstrap

import (
    "log"

    "github.com/spf13/viper"
)

type Env struct {
    AppEnv                 string `mapstructure:"APP_ENV"`
    ServerAddress          string `mapstructure:"SERVER_ADDRESS"`
    ContextTimeout         int    `mapstructure:"CONTEXT_TIMEOUT"`
    DBHost                 string `mapstructure:"DB_HOST"`
    DBPort                 string `mapstructure:"DB_PORT"`
    DBUser                 string `mapstructure:"DB_USER"`
    DBPass                 string `mapstructure:"DB_PASS"`
    DBName                 string `mapstructure:"DB_NAME"`
    AccessTokenExpiryHour  int    `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR"`
    RefreshTokenExpiryHour int    `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"`
    AccessTokenSecret      string `mapstructure:"ACCESS_TOKEN_SECRET"`
    RefreshTokenSecret     string `mapstructure:"REFRESH_TOKEN_SECRET"`
}

func NewEnv() *Env {
    env := Env{}
    viper.SetConfigFile(".env")

    err := viper.ReadInConfig()
    if err != nil {
        log.Fatal("Can't find the file .env : ", err)
    }

    err = viper.Unmarshal(&env)
    if err != nil {
        log.Fatal("Environment can't be loaded: ", err)
    }

    if env.AppEnv == "development" {
        log.Println("The App is running in development env")
    }

    return &env
}

After that, we can just have the Env as below:

env := NewEnv()

Finally, we can just access the configuration as below:

dbName := env.DBName

secret := env.AccessTokenSecret

So, this way we are able to access the configuration values.

I will highly recommend going through the project and checking the usage of viper to load the configuration.

Link to the project: Go Backend Clean Architecture.

This is how we can load the configurations from the envfile in Golang using Viper in Go (Golang) projects.

That's it for now.

Thanks

Amit Shekhar

Co-Founder @ Outcome School

You can connect with me on:

Read all of our blogs here.