There are multiple ways to configure your Go application. I will describe a few most common one in this article with code samples.
Flags
Go supports command line flags with builtin package flag.
package main
import (
"flag"
"log"
"net/http"
)
func main() {
addrPtr := flag.String("addr", ":8000", "addr of http server")
flag.Parse()
log.Printf("Listening on %s", *addrPtr)
log.Fatal(http.ListenAndServe(*addrPtr, nil))
}
func main() {
var addr string
flag.StringVar(&addr, "addr", ":8000", "addr of http server")
flag.Parse()
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
You can use flag.String
flag.Int
and so on to return a pointer to variable. If you prefer you can bind the flag to a variable with Var() functions (as for example flag.StringVar
flag.IntVar
. The full list is here.
go run main.go -addr :8888
2023/11/05 20:52:28 Listening on :8888
POSIX-style –flags
pflag is a drop-in replacement for Go’s flag package, implementing POSIX/GNU-style –flags
package main
import (
"github.com/spf13/pflag"
"log"
"net/http"
)
func main() {
addr := pflag.String("addr", ":8000", "addr of http server")
pflag.Parse()
log.Printf("Listening on %s", *addr)
log.Fatal(http.ListenAndServe(*addr, nil))
}
go run main.go --addr :8888
2023/11/05 23:03:49 Listening on :8888
Environment variables
According to The Twelve-Factor App the application should be configurable with environment variables.
import (
"http"
"log"
"os"
)
func main() {
addr := os.Getenv("ADDR")
if addr == "" {
addr = ":8000"
}
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
ADDR=8888 go run main.go
2023/11/05 20:54:35 Listening on :8888
Viper
Viper is a full solution for app configuration. It supports:
- defaults
- multiple formats including JSON, TOML, YAML, HCL, envfile and Java properties config files
- live watching and re-reading of config files (optional)
- reading from environment variables
- reading from remote config systems (etcd or Consul), and watching changes
- reading from command line flags
- reading from buffer
Reading from files and envvars
package main
import (
"fmt"
"log"
"net/http"
"github.com/spf13/viper"
)
func main() {
viper.SetDefault("addr", ":8000")
viper.AddConfigPath("./config")
viper.SetConfigName("default")
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
log.Fatal(fmt.Errorf("fatal error config file: %w", err))
}
addr := viper.GetString("addr")
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
// config/default.yaml
ADDR: ":8888"
Unmarshaling the config into a struct
Viper has an ability to unmarshal config to a struct or map.
package main
import (
"fmt"
"log"
"net/http"
"github.com/spf13/viper"
)
type config struct {
Addr string
}
func main() {
// ...
var c config
err = viper.Unmarshal(&c)
if err != nil {
log.Fatal(fmt.Errorf("error unmarshalling: %w", err))
}
log.Printf("Listening on %s", c.Addr)
log.Fatal(http.ListenAndServe(c.Addr, nil))
}
Combining Viper and pflag full example
Viper supports binding to flags with pflag package. This way you can configure the app by:
- flag
- environment variable
- file
package main
import (
"fmt"
"log"
"net/http"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type config struct {
Addr string
}
func main() {
pflag.String("addr", ":8000", "addr of http server")
pflag.Parse()
viper.SetDefault("addr", ":8000")
err := viper.BindPFlags(pflag.CommandLine)
if err != nil {
log.Fatal(fmt.Errorf("fatal binding pflags: %w", err))
}
viper.AddConfigPath("./config")
viper.SetConfigName("default")
viper.AutomaticEnv()
err = viper.ReadInConfig()
if err != nil {
log.Fatal(fmt.Errorf("fatal error config file: %w", err))
}
var c config
err = viper.Unmarshal(&c)
if err != nil {
log.Fatal(fmt.Errorf("error unmarshalling: %w", err))
}
log.Printf("Listening on %s", c.Addr)
log.Fatal(http.ListenAndServe(c.Addr, nil))
}