Environment variables in Golang


This tutorial assumes you have:

  • A basic understanding of Go Language
  • Latest Golang version installed on your system
  • A few minutes of your time.

In this article, we will know about environment variables and why to use them. And will access them in a Go application using inbuilt and third-party packages.

What are environment variables?

Environment variables are key-value pair on a system-wide level, and running processes can access that. These are often used to make the same program behave differently in different deployment environments like PROD, DEV, or TEST. Storing configuration in the environment is one of the principles of a twelve-factor app. It enables applications to be portable.

Why should you use environment variables

  • If you are using the sensitive information in the code, then all the unauthorized users who have access to the code will have sensitive data, you might not want that.
  • If you are using the code versioning tool like git, you may push your DB credentials with the code, and it will become public.
  • If you are managing variables in one place, in case of any changes, you don't have to change it in all the places in application code.
  • You can manage multiple deployment environments like PROD, DEV, or TEST. Environment variables are easy to change between deploys without changing any application code.

Never forget to include your environment variable files in the .gitignore

Inbuilt OS package

You don't need any external package to access the environment variables in Golang, and you can do that with the standard os package. Below is the list of functions related to environment variables and there uses.

  • os.Setenv() sets the value of an environment value.
  • os.Getenv() gets the value environment variable named by the key.
  • os.Unsetenv() delete a single environment value named by the key, if we try to get that environment value using os.Getenv() it will return an empty value.
  • os.ExpandEnv replaces ${var} or $var in the string as per the values of environment variables. If any environment variable is not present an empty string will replace it.
  • os.LookupEnv() gets the value environment variable named by the key. If the variable is not present in the system, the returned value will be empty, and the boolean will be false. Otherwise, it returns the value (which can be empty), and the boolean is true.

os.Getenv() will return an empty string if the environment variable is not present, to distinguish between an empty value and an unset value, use LookupEnv.

Now let's use all the above functions in our code. Create a main.go file in an empty folder.

package main

import (
 "fmt"
 "os"
)

func main() {
 // Set Environment Variables
 os.Setenv("SITE_TITLE", "Test Site")
 os.Setenv("DB_HOST", "localhost")
 os.Setenv("DB_PORT", "27017")
 os.Setenv("DB_USERNAME", "admin")
 os.Setenv("DB_PASSWORD", "password")
 os.Setenv("DB_NAME", "testdb")

 // Get the value of an Environment Variable
 host := os.Getenv("SITE_TITLE")
 port := os.Getenv("DB_HOST")
 fmt.Printf("Site Title: %s, Host: %s\n", host, port)

 // Unset an Environment Variable
 os.Unsetenv("SITE_TITLE")
 fmt.Printf("After unset, Site Title: %s\n", os.Getenv("SITE_TITLE"))

 //Checking that an environment variable is present or not.
 redisHost, ok := os.LookupEnv("REDIS_HOST")
 if !ok {
 fmt.Println("REDIS_HOST is not present")
 } else {
 fmt.Printf("Redis Host: %s\n", redisHost)
 }

 // Expand a string containing environment variables in the form of $var or ${var}
 dbURL := os.ExpandEnv("mongodb://${DB_USERNAME}:${DB_PASSWORD}@$DB_HOST:$DB_PORT/$DB_NAME")
 fmt.Println("DB URL: ", dbURL)
}

Below is the output when we run go run main.go in our terminal

go run main.go

//output
Site Title: Test Site, Host: localhost
After unset, Site Title: 27017
REDIS_HOST is not present
DB URL: mongodb://admin:password@localhost:27017/testdb

There are two more functions os.Clearenv and os.Environ() let's use them also in a separate program.

  • os.Clearenv deletes all environment variables, It can be useful to clean up the environment for tests
  • os.Environ() returns a slice of the string containing all the environment variables in the form of key=value.
package main

import (
 "fmt"
 "os"
 "strings"
)

func main() {

 // Environ returns a slice of string containing all the environment variables in the form of key=value.
 for _, env := range os.Environ() {
 // env is
 envPair := strings.SplitN(env, "=", 2)
 key := envPair[0]
 value := envPair[1]

 fmt.Printf("%s : %s\n", key, value)
 }

 // Delete all environment variables
 os.Clearenv()

 fmt.Println("Number of environment variables: ", len(os.Environ()))
}

The above function will list all the environment variables available in the system, including NAME and DB_HOST. Once we run os.Clearenv() it will clear all the environment variables for the running process.

godotenv package

The Ruby dotenv project inspires GoDotEnv package, it loads the environment variables from a .env file

Let's create a .env file in which we will have all our configurations.

# .env file
# This is a sample config file

SITE_TITLE=Test Site 

DB_HOST=localhost
DB_PORT=27017
DB_USERNAME=admin
DB_PASSWORD=password
DB_NAME=testdb

Then in the main.go file we will use godotenv to load the environment variables.

we can load multiple env files at once also. godotenv also supports YAML.

// main.go
package main

import (
 "fmt"
 "log"
 "os"

 "github.com/joho/godotenv"
)

func main() {

 // load .env file from given path
 // we keep it empty it will load .env from current directory
 err := godotenv.Load(".env")

 if err != nil {
 log.Fatalf("Error loading .env file")
 }

 // getting env variables SITE_TITLE and DB_HOST
 siteTitle := os.Getenv("SITE_TITLE")
 dbHost := os.Getenv("DB_HOST")

 fmt.Printf("godotenv : %s = %s \n", "Site Title", siteTitle)
 fmt.Printf("godotenv : %s = %s \n", "DB Host", dbHost)
}

Open the terminal and run the main.go

go run main.go

// output
godotenv : Site Title = Test Site
godotenv : DB Host = localhost

Viper package

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

Viper supports many file formats to load environment variables, e.g., Reading from JSON, TOML, YAML, HCL, envfile and Java properties config files. So in this example, we will look at how to load environment variables from a YAML file.

YAML is a human-readable data-serialization language. It is commonly used for configuration files and in applications where data is being stored or transmitted.

Let's create our config.yaml and main.go in an empty folder.

# config.yaml
SITE:
 TITLE: Test Site

DB:
 HOST: "localhost"
 PORT: "27017"
 USERNAME: "admin"
 PASWORD: "password"
 NAME: "testdb"

In the below code, we are using Viper to load environment variables from a config.yaml. We can load the config file from any path we want. We can also set the default values for any environment variable if any environment variable is not available in the config file.

// main.go
package main

import (
 "fmt"
 "log"
 "os"

 "github.com/spf13/viper"
)

func main() {

 // Set the file name of the configurations file
 viper.SetConfigName("config")

 // Set the path to look for the configurations file
 viper.AddConfigPath(".")

 // Enable VIPER to read Environment Variables
 viper.AutomaticEnv()

 viper.SetConfigType("yml")

 if err := viper.ReadInConfig(); err != nil {
 fmt.Printf("Error reading config file, %s", err)
 }

 // Set undefined variables
 viper.SetDefault("DB.HOST", "127.0.0.1")

 // getting env variables DB.PORT
 // viper.Get() returns an empty interface{}
 // so we have to do the type assertion, to get the value
 DBPort, ok := viper.Get("DB.PORT").(string)

 // if type assert is not valid it will throw an error
 if !ok {
 log.Fatalf("Invalid type assertion")
 }

 fmt.Printf("viper : %s = %s \n", "Database Port", DBPort)
}

Open the terminal and run the main.go

go run main.go

// output
viper : Database Port = 27017

Conclusion

Using environment variables is an excellent way to handle configuration in our application. Overall, it provides you with easy configuration, better security, multiple deployment environments and fewer production mistakes.

Now you can manage environment variables in your go application, and You can found the complete code used in this tutorial on our Github Repo