Handling configuration in Go

Image for post
Image for post

The 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.

DB_PASS="db_pass" go run main.go

Access 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 cert and key file paths that I am passing to my application.

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 separate our development configuration from our production configuration.

We would have to parse the TOML file in order to access our configuration variables. I use github.com/BurntSushi/toml to parse TOML in all my go applications.

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.

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.

Written by

University of Toronto, Computer Engineering, architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store