Handling configuration in Go

Configuration is key especially when your application starts to get a little bigger. You might need to provide parameters such as keys, passwords, urls etc to your go application.
If any of the parameters are sensitive (such as a database password), it should not be kept in your config file which is usually committed and kept in plain text. Sensitive parameters should be served as runtime env variables.
Pass your env variables
DB_PASS="db_pass" go run main.go
Access to your env variables
fmt.Println("DB_PASS:", os.Getenv("DB_PASS"))

Using TOML for configuration

I usually like to use TOML for my Go application configuration. TOML is very easy to use and works really well with Go. Here is an example TOML file I might use. You can see the SSL crt and key file paths that I am passing to my application.
config.toml
api_addr = ":3000"

[certs]
crt_file = "main.crt"
key_file = "main.key"
I think it is easier to keep two config files, one for development and one for production. Let's just name them "config.dev.toml" and "config.toml". This way, we would be able to seperate our development configuration from our production configuration.
We would have to parse the TOML file in order to access our configuration variables. I usegithub.com/BurntSushi/tomlto parse TOML in all my go applications.
config.go
package main

import (
  "log"

  "github.com/BurntSushi/toml"
)

type certsConfig struct {
  CrtFile string `toml:"crt_file"`
  KeyFile string `toml:"key_file"`
}

type tomlConfig struct {
  APIAddr     string        `toml:"api_addr"`
  Certs       certsConfig   `toml:"certs"`
}

var (
  config     tomlConfig
  configPath string
)

func loadConfig() {
  if _, err := toml.DecodeFile(configPath, &config); err != nil {
    log.Fatalln("Reading config failed", err)
  }
}
Since we have two config files (one for development and one for production), we can pass which one we would like our application to use as a flag when starting the application.
I prefer parsing flags in the init function of the application. As you can see below, the default config file is the development config file.
main.go
package main

import (
  "flag"
  "log"
)

func init() {
  // Path to config file can be passed in.
  flag.StringVar(&configPath, "config", "config.dev.toml", "Path to config file")
  flag.Parse()

  loadConfig()
}

func main() {
  log.Println("Application is running at", config.APIAddr)
}
And in order to run the application in production with the production config, pass the config path as a flag.
DB_PASS="db_pass" go run main.go --config="config.toml"
That's pretty much it. Same idea can be applied with YAML and even with json as the config file type. But I am pretty happy with TOML.
Thanks for stopping by!
Koray Gocmen
Koray Gocmen

University of Toronto, Computer Engineering.

Architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.