Load configuration from file, environment variables and the command line (flags) with Golang
Tags: go, programming, tutorial
Published: 2022-09-18 06:21:46

---
title: Load configuration from file, environment variables and the command line (flags) with Golang
published: true
description: 
tags: go, programming, tutorial
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i0t904mqczoaqd5vaw6.png
# Use a ratio of 100:42 for best results.
series: Programs configurations
---

You are looking for [a small library](gitlab.schukai.com/oss/libraries/go/application/configuration
) to read values from configurations, you use YAML, TOML, JSON or properties? 

You want to be able to read and change the configuration via HTTP?

Furthermore, you would like to react to changes in the configuration file?

Then you might find what you are looking for here.

## What do this library?

This library provides a simple way to load configuration from different sources.

It supports:

* [x] Environment variables
* [x] Command line flags
* [x] Configuration files
  * [x] JSON
  * [x] YAML
  * [x] TOML
  * [x] Properties
* [x] Configuration from a struct
* [x] HTTP API (get and set configuration)
* [x] Monitor File changes


## Installation

```shell
go get \
gitlab.schukai.com/oss/libraries/go/application/configuration
```

**Note:** This library uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.

## Usage

### Initialize

A new configuration is created using the `configuration.New()` function. The passed
structure is used type for the configuration. The values are taken as default values
of the configuration.

```go
package main

import (
    "fmt"
    "os"
    "gitlab.schukai.com/oss/libraries/go/application/configuration"
)

func main(){
  config := struct {
    Host string
    Port int
  }{
    Host: "localhost",
    Port: 8080,
  }

  c := configuration.New(config)

  fmt.Println(c.Config().Host)
  fmt.Println(c.Config().Port)

}

```

Configuration values can come from different sources. The order of
sources is important.

#### Environment variables

With the `Environment` function, you can load configuration from environment variables.

```go
package main

import (
    "fmt"
    "os"
    "gitlab.schukai.com/oss/libraries/go/application/configuration"
)

func main(){
  config := struct {
    Host string `env:"HOST"`
  }{
    Host: "localhost",
  }

  // Set value
  os.Setenv("HOST", "www.example.com")

  s := configuration.New(config)
  fmt.Println(s.Config().Host) // localhost

  s.InitFromEnv("") // no prefix
  fmt.Println(s.Config().Host) // www.example.com

}

```

#### Command line flags

Obviously, you can also load configuration from command line flags. This library supports
the standard library [flag](https://golang.org/pkg/flag/) package.

```go
package main

import (
    "fmt"
    "os"
   "flag"
    "gitlab.schukai.com/oss/libraries/go/application/configuration"
)

func main(){
  config := struct {
    Host string `flag:"host"`
  }{
    Host: "localhost",
  }

  // Set value
  flag.String("host", "www.example.com", "help message for host flag")
  flag.Parse()

  s := configuration.New(config)
  s.InitFromFlagSet(flag.CommandLine)

  fmt.Println(s.Config().Host) // www.example.com

}

```

Do you want to allow the user to specify a configuration via the command line,
so you can use the `AddFileFromFlagSet` function. This function expects a `flag.FlagSet`.


### Import files and streams

You can load configuration from files and streams. With the `Import()` function you
import files and streams. The specified files are loaded first, and then the
streams.

The configurations are merged. If a value is already set, it is overwritten by the
specified value. So if a value is set in `etc` and in the more specific
File in the user home directory, the value is taken from the user home directory.

```go
package main

import (
   "fmt"
   "os"
   "flag"
   "gitlab.schukai.com/oss/libraries/go/application/configuration"
)

func main() {

   config := struct {
      Host string
   }{
      Host: "localhost",
   }

   c := configuration.New(config)

   c.SetMnemonic("my-app")
   c.SetDefaultDirectories()
   c.Import()

   fmt.Println(c.Config().Host)

}


```

The configuration would then be looked for in the following places:

* ~/config.yaml   (working directory)
* ~/.config/my-app/config.yaml
* /etc/my-app/config.yaml

#### Configuration files

Configuration files are certainly the most common way to define configurations.
These can be in different formats. The following formats are currently supported:
JSON, YAML, TOML and Properties.

With files, the approach is slightly different. Here the function `AddDirectory()`
first, specify directories in which to search for the file. The file
is then searched for in the order of the directories. If the file is found
is loaded.

The helper function `AddWorkingDirectory()` adds the current working directory.

With `AddEtcDirectory()` the directory `etc` is added under Unix.

`AddUserConfigDirectory()` adds the directory `~.config` on Unix. It will
uses the `os.UserConfigDir()` method. Thus, it is possible to use the directory
can also be used on other operating systems.

The `SetDefaultDirectories()` method sets the paths to the default values. Become internal
the paths with `AddWorkingDirectory()`, `AddEtcDirectory()` and `AddUserConfigDirectory()`
set.

The directory structure can be specified directly with the `SetDirectories()` function.
This overwrites the directory structure specified with `AddDirectory()`.

The filename of the configuration file is specified with `SetFileName()`. The file name
is searched for with the extension `.json`, `.yaml`, `.yml`, `.toml` or `.properties`.

If a format is specified with the `SetFileFormat()` method, only files with
searched for this ending.

As an extra, you can specify your own file system with `SetFilesystem()`.
This is useful for testing.

Furthermore, the `AddFile()` function can be used to add a file directly. 
This makes sense if you are given a file as a parameter
or if you don't want to have a multi-level structure.

**Watch files**

If you want to watch configuration files for changes, you can use the `Watch()` function.
With the function `StopWatch()` you can stop watching the files. The function
returns a bool channel on which the status of the watch is reported. 

If the watch has ended, a true is sent over the channel. If an error occurs, a false is sent.

#### Streams

The configuration can also be loaded from a stream. This is useful if you want to
load the configuration from other sources. For example, from a database.

With the `AddReader()` function, you can add a stream to the configuration. The
stream is then searched for in the order of the streams. If the stream is found
is loaded.

### HTTP API

The configuration can also be changed via HTTP. This is useful if you want to
change the configuration at runtime. For example, if you would like to change the
configuration of a service.

With a Get request, the configuration is returned as the format specified in the
`Accept` header. If no format is specified, the configuration is returned in JSON format.

With a Post request, the configuration can be changed. The configuration is then
taken from the body of the request. The format is determined by the `Content-Type`
header. If no format is specified, the configuration is taken from the JSON format.


```go
config := struct {
   Host string
}
s := New(config)
mux := http.NewServeMux()
mux.HandleFunc("/config", s.ServeHTTP)

```

There is also a middleware to get access to the configuration.

```go
config := struct {
    Host string
}
s := New(config)
mux := http.NewServeMux()
mux.Use(s.Middleware)
```

The configuration can then be accessed via the `config` context.

```go
func handler(w http.ResponseWriter, r *http.Request) {
	config := r.Context().Value("config").(struct {
		Host string
	})
	fmt.Println(config.Host)
}


```

### On change

If you want to be notified when the configuration changes, you can use the
`OnChange()` function. This function takes a callback function as a parameter.

The following program gives the following output `Change from localhost to www.example.com`.

```go
package main

import (
   "fmt"
   "os"
   "flag"
   "gitlab.schukai.com/oss/libraries/go/application/configuration"
)

func main() {
   config := struct {
      Host string
   }{
      Host: "localhost",
   }

   s := configuration.New(config)

   closeChan := make(chan bool)

   s.OnChange(func(event configuration.ChangeEvent) {
      log := event.Changlog
      msg := fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
      fmt.Println(msg)
      closeChan <- true
   })

   c := s.Config()
   c.Host = "www.example.com"

   s.SetConfig(c)

   // Wait for change
   select {
   case <-closeChan:
   }

}


```

### Error handling

If an error occurs, it is returned by the function `Errors()`. The errors can be handled as usual.

The `HasErrors()` function can be used to check whether errors have occurred.

---

Voila!