Handling Configuration Files in Go

I’ve been working on a number of Go applications recently, and I was looking for the best or preferred way to read data from a configuration file. But it turns out, there is no preferred way of doing it. I’ve looked into several Go projects in Github, and most of them use different solutions and different configuration file formats.

Originally, I was planning to implement my configuration file in JSON format, but one of my gripes with JSON is that you cannot add comments to it. Adding comments and documentation to a configuration file is very important as it lets anyone to easily understand the parameters used by the application. JSON is very easy to parse, but as your configuration file grows, it becomes difficult to read because comments are not permitted. Another popular format is YAML, which very easy to read. But my main issue with it is that it is very sensitive to indentation and writing a parser is more difficult to write.

The format I prefer using these days is TOML. It’s very easy to read, allows comments, and not sensitive to indentation. Here’s a sample TOML file:

key1 = “TOML example" multi_line = “””multi-line string use””" int = 666 float = 9.0 date = 2016-02-02 bool = true
key2 = “lemmy" key3 = “motorhead"

A Brief Intro to TOML

While JSON have objects and YAML have associative arrays, TOML has a similar collection of key/value pairs called tables. They appear as keys with square brackets, and you can easily distinguish them from arrays because arrays are always values. Under a table, are key/value pairs associated to that table. In the example above, key2 and key3 are associated with the table-2 table.

TOML format is much easier to read and write, and relatively easy to parse. What’s great about it is that it has support for comments and not sensitive to indentation or whitespaces. I was planning to write my own parser for TOML but a quick search led me to finding Viper, a pretty robust solution for handling configuration files in Go.

Using Viper

Viper is a complete configuration solution, and has support for configuration files in JSON, YAML, and TOML. In this post, I’ll demonstrate how to read a configuration file in TOML using Viper. For clarity’s sake, the following Go code will just read data from a TOML config file and display them:

# A sample TOML config file.
[development]
server = "192.168.1.1"  
port = 8080  
connection_max = 100  
enabled = true

[staging]
server = "192.168.1.2"  
port = 8090  
connection_max = 200  
enabled = true

[production]
server = "192.168.1.3"  
port = 9000  
connection_max = 5000  
enabled = true  

And here’s the Go code on how to read our TOML config file.

package main

import (  
  "fmt"
  "github.com/spf13/viper"
)

func main() {

  viper.SetConfigName("app")     // no need to include file extension
  viper.AddConfigPath("config")  // set the path of your config file

  err := viper.ReadInConfig()
  if err != nil {
    fmt.Println("Config file not found...")
  } else {
    dev_server := viper.GetString("development.server")
    dev_connection_max := viper.GetInt("development.connection_max")
    dev_enabled := viper.GetBool("development.enabled")
    dev_port := viper.GetInt("development.port")

    fmt.Printf("\nDevelopment Config found:\n server = %s\n connection_max = %d\n" +
        " enabled = %t\n" +
        " port = %d\n",
        dev_server,
        dev_connection_max,
        dev_enabled,
        dev_port)

    prod_server := viper.GetString("production.server")
    prod_connection_max := viper.GetInt("production.connection_max")
    prod_enabled := viper.GetBool("production.enabled")
    prod_port := viper.GetInt("production.port")

    fmt.Printf("\nProduction Config found:\n server = %s\n connection_max = %d\n" +
        " enabled = %t\n" +
        " port = %d\n",
        prod_server,
        prod_connection_max,
        prod_enabled,
        prod_port)
  }
}

One important thing to notice here is that I did not have to use the file extension of my configuration file when I passed app.toml to the function SetConfigName(). The function can automatically detect the file format whether it is JSON, YAML, or TOML. Also, notice that the keys inside a table is accessed using the dot notation (development.server), similar to how you access object properties in JSON.

And this is how I load my configuration files in Go. TOML is now my preferred configuration language for writing all of my applications, and Viper is my library of choice for handling configuration files in Go.

You may also check out the source code of the example on GitHub.