Loading .env files in Go with Viper
I do not like os.Getenv scattered through a codebase. The variable names end up duplicated in a dozen places and a typo in one of them is a runtime surprise. In most Go projects I read the .env file once at startup into a struct using Viper, and the rest of the code takes the struct. This is the setup I copy from project to project.
go get github.com/spf13/viper
One package holds the struct and the loader:
package configs
import (
"log"
"github.com/spf13/viper"
)
// Env holds the environment variables
type Env struct {
Port string `mapstructure:"PORT"`
MongoURL string `mapstructure:"MONGO_URI"`
RedisURL string `mapstructure:"REDIS_URI"`
SecretToken string `mapstructure:"SECRET_TOKEN"`
RefreshToken string `mapstructure:"REFRESH_TOKEN"`
}
// LoadConfig reads the .env file and unmarshals it into Env
func LoadConfig() *Env {
var envs Env
viper.AddConfigPath(".")
viper.SetConfigName(".env")
viper.SetConfigType("env")
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
}
if err := viper.Unmarshal(&envs); err != nil {
log.Fatalf("Unable to decode into struct, %v", err)
}
return &envs
}
Then main loads it once and passes it along:
package main
import (
"fmt"
"myapp/configs"
)
func main() {
envs := configs.LoadConfig()
fmt.Printf("Port: %s\n", envs.Port)
fmt.Printf("MongoURL: %s\n", envs.MongoURL)
fmt.Printf("RedisURL: %s\n", envs.RedisURL)
fmt.Printf("SecretToken: %s\n", envs.SecretToken)
fmt.Printf("RefreshToken: %s\n", envs.RefreshToken)
}
The mapstructure tags map each field to a variable name, so there is one place where the names are spelled out. If a key is misspelled, the field is empty when the config is printed at startup, not somewhere deep inside a request handler.
Sign in with GitHub to comment