Golang TOML Configs Example: MySQL Connection

Let’s have a look at how to split application code and configuration for your Golang app. As an example I’d like to show how to use TOML to load your database configuration.

I came to write this because I was dabbling with exporting WordPress posts to TOML front matter / hugo and needed to be able to swap out database servers for development and production servers.

TLDR; If you want to skip all the jabber and code explanation, just scroll straight to the bottom.

Golang TOML parsing

TOML is a file format, a bit like JSON, that supports a simple and human readable key-value store, perfect for config files.

To parse TOML, I tend to use BurntSushi/toml, because it’s a solid and simple library and supports reading files without writing your own Reader.

My example config file:

# filename: config.toml
[database]
server = "127.0.0.1"
port = "3306"
database = "jonathanmh_com"
user = "jonathanmh"
password = "some_super_strange_long_password"

[output]
directory = "./output"
format = "toml"

Let’s have a look at which struct we could parse that into:

type Config struct {
  Output output
  Database database
}

type database struct {
  Server string
  Port string
  Database string
  User string
  Password string
}

type output struct {
  Directory string
  Format string
}

In order to parse the config file into the struct and its to sub-structs, we merely need to create a new variable for the config, in this case conf and use toml.DecodeFile:

var conf Config
if _, err := toml.DecodeFile("./config.toml", &conf); err != nil {
  fmt.Println(err)
}
fmt.Printf("%#v\n", conf)

Golang String Interpolation

In order to make sure you have the right order and format and instead of using the + operator and have a big mess in your configuration sensitive code, let’s go ahead and make use of fmt.Sprintf which basically works like Printf, but lets you save the output to a variable.

connString := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", conf.Database.User, conf.Database.Password, conf.Database.Server, conf.Database.Port, conf.Database.Database)

db, err := sql.Open("mysql", connString)

if err != nil {
  fmt.Println(err.Error())
}
defer db.Close()

Now let’s go ahead and test the connection with a simple query (that doesn’t require you to create any tables)

var count int

err = db.QueryRow("SELECT 1 + 1").Scan(&count)
if err != nil {
  panic(err)
}
fmt.Println(count)

The output should be 2, if you configuration is correct, the database server running and so on.

Summary

That’s it! A quick and pragmatic example how to use config files in TOML to save things like your database configuration in your Go(lang) project! As a quick tip, config files should not be tracked with git, so I would always put the config.toml into the .gitignore file, but keep a config.example.toml around that illustrates how it works and which values need to be filled in.

How to you manage environment and configuration? Let me know in the comments!

See the full source below, don’t forget the config.toml further up 😉

package main

import(
  "database/sql"
  "fmt"

  _ "github.com/go-sql-driver/mysql"
  "github.com/BurntSushi/toml"
)

type Config struct {
  Output output
  Database database
}

type database struct {
  Server string
  Port string
  Database string
  User string
  Password string
}

type output struct {
  Directory string
  Format string
}

func main(){
  var conf Config
  if _, err := toml.DecodeFile("./config.toml", &conf); err != nil {
    fmt.Println(err)
  }
  fmt.Printf("%#v\n", conf)

  connString := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", conf.Database.User, conf.Database.Password, conf.Database.Server, conf.Database.Port, conf.Database.Database)

  db, err := sql.Open("mysql", connString)

  if err != nil {
    fmt.Println(err.Error())
  }

  defer db.Close()

  var count int

  err = db.QueryRow("SELECT 1 + 1").Scan(&count)
  if err != nil {
    panic(err)
  }
  fmt.Println(count)
}

Leave a Reply

Your email address will not be published. Required fields are marked *