gonfig is a flexible and extensible configuration library designed to simplify working with application settings.
It supports loading configurations from environment variables, command-line flags, and various configuration file formats.
Additionally, it offers an easy way to extend support for new formats. One of its key features is the ability to replace
or customize components, such as using spf13/pflag instead of the standard flag package from the Go standard library.
This library simplifies configuration management, making it easy to define, override, and merge settings in your applications.
- Multiple Sources – Load configurations from config-files, flags, environment variables, or custom sources.
- Easy to Use – Simple API for defining and managing configurations.
- Extensible – You can implement custom loaders to fit your needs.
- Lightweight – No unnecessary dependencies, optimized for performance.
The priority described below is considered the default priority and can be modified through configuration settings.
-
Defaults — These are basic configuration values embedded in the application's code. They ensure the application can run even if no external configurations are provided.
-
Environment Variables — Environment variables are usually used to configure deployment-related parameters (e.g., logins, ports, database addresses). These variables often have a higher priority as they can be dynamically set depending on the environment.
-
Flags — Command-line flags usually have the highest priority since they allow direct overriding of any settings at application startup. This is useful when a quick configuration change is needed without modifying the code or config files.
-
Config File — A configuration file stored on disk, typically containing predefined parameters for a specific environment. This can be in formats like JSON, YAML, TOML, etc.
-
Remote Config — This is a configuration retrieved from external sources, such as configuration servers or cloud services (e.g., Consul, Etcd, or AWS SSM). These systems usually allow centralized management of settings across different applications.
go get github.com/im-kulikov/gonfigNote: gonfig uses Go Modules to manage dependencies.
- Defaults — Set in the code. For example, the default server port is
8080. - Environment Variables — Environment variables can be used to set database connections or other services linked to the environment.
- Flags — Command-line arguments always have the highest priority, as they can be specified at application startup to override any other parameter.
- Config File — The configuration file specifies more detailed parameters, such as database connections or the application's operating mode.
- Remote Config — Configuration retrieved from a remote server can override settings from the config file.
- Load defaults
- Load environments
- Load flags
- Mark as required
- Load YAML
WithYAMLLoader - Load JSON
WithJSONLoader - Load TOML
WithTOMLLoader - Other formats, you can write it using custom loader
package main
import (
"fmt"
"github.com/im-kulikov/gonfig"
)
type Config struct {
Config string `flag:"config,short:c,config:true"`
Field string `flag:"field" env:"FIELD" default:"default-value" usage:"description for flags" require:"true"`
}
// go run /path/to/main/folder --config /path/to/config.yml
func main() {
var cfg Config
if err := gonfig.Load(&cfg,
gonfig.WithYAMLLoader(),
gonfig.WithDefaults(gonfig.FlagTag, map[string]any{
"field": "some-custom-default-value",
})); err != nil {
panic(err)
}
fmt.Printf("Loaded config: %+v\n", cfg)
}package main
import (
"fmt"
"github.com/im-kulikov/gonfig"
)
type Config struct {
Field string `flag:"field" env:"FIELD" default:"default-value" usage:"description for flags" require:"true"`
}
func main() {
var cfg Config
if err := gonfig.New(gonfig.Config{}).Load(&cfg); err != nil {
panic(err)
}
fmt.Printf("Loaded config: %+v\n", cfg)
}package main
import (
"fmt"
"github.com/im-kulikov/gonfig"
)
type Config struct {
Config string `flag:"config,short:c,config:true"`
Port int `yaml:"port"`
}
// go run /path/to/main/folder --config /path/to/config.yml
func main() {
var cfg Config
if err := gonfig.Load(&cfg, gonfig.WithYAMLLoader()); err != nil {
panic(err)
}
fmt.Printf("Loaded config: %+v\n", cfg)
}package main
import (
"fmt"
"github.com/im-kulikov/gonfig"
)
type Config struct {
Config string `flag:"config,short:c,config:true"`
Port int `toml:"port"`
}
// go run /path/to/main/folder --config /path/to/config.toml
func main() {
var cfg Config
if err := gonfig.Load(&cfg, gonfig.WithTOMLLoader()); err != nil {
panic(err)
}
fmt.Printf("Loaded config: %+v\n", cfg)
}package main
import (
"fmt"
"github.com/im-kulikov/gonfig"
)
type Config struct {
Config string `flag:"config,short:c,config:true"`
Port int `json:"port"`
}
// go run /path/to/main/folder --config /path/to/config.json
func main() {
var cfg Config
if err := gonfig.Load(&cfg, gonfig.WithJSONLoader()); err != nil {
panic(err)
}
fmt.Printf("Loaded config: %+v\n", cfg)
}You can implement your own configuration loaders by implementing the Parser interface.
type CustomLoader struct {}
func (c *CustomLoader) Load(dest interface{}) error {
// Custom loading logic here
return nil
}
func (c *CustomLoader) Type() gonfig.ParserType {
return "custom-loader"
}or with config-path defining
type CustomLoader struct {
path string
}
func (c *CustomLoader) SetPath(path string) { c.path = path }
func (c *CustomLoader) Load(dest interface{}) error {
// Custom loading logic here
// for example - json / toml / etc
return nil
}
func (c *CustomLoader) Type() gonfig.ParserType {
return "custom-loader"
}Introduced in Issue #3
A new Go interface, LoaderValidator. Any struct that implements this interface will have its Validate() method called automatically by the loading mechanism.
-
Define the interface:
type LoaderValidator interface { Validate() error }
-
Integrated into the load process: We modified the existing configuration loading function(s) to include a check for this interface. The execution flow should be:
- Load the configuration (e.g., from YAML/JSON).
- Run basic validation (e.g., the existing
ValidateRequiredFields). - Check if the loaded config struct implements
LoaderValidator. - If it does, call the
config.Validate()method and return any error it produces.
A user can now easily add sophisticated validation to their config:
// User's configuration struct
type AppConfig struct {
Port int `yaml:"port" required:"true"`
Username string `yaml:"username" required:"true"`
Email string `yaml:"email"`
}
// Implement the LoaderValidator interface
func (c *AppConfig) Validate() error {
// Custom validation 1: Check if port is in the valid range
if c.Port < 1024 || c.Port > 65535 {
return fmt.Errorf("port must be between 1024 and 65535, got %d", c.Port)
}
// Custom validation 2: Check email format if provided
if c.Email != "" {
if !isValidEmail(c.Email) {
return fmt.Errorf("invalid email format: %s", c.Email)
}
}
return nil
}
// Helper function (user-defined)
func isValidEmail(email string) bool {
// ... simple regex check for example purposes
re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
return re.MatchString(email)
}With this implementation, when the user loads their AppConfig, the system will automatically check that Port
and Username are provided (basic validation) and then run the custom checks to ensure the port number is acceptable
and the email format is valid.
- Backward Compatible: This is a purely additive change. Existing code without the
Validate()method will continue to work unchanged. - Clean and Idiomatic: It follows Go's common pattern of using interfaces for extensibility (e.g.,
Stringer,Error). - Powerful and Flexible: Users are no longer limited to just
requiredchecks and can implement any validation logic their application requires (cross-field validation, business logic, formatting, etc.). - Centralized Validation: The validation logic lives alongside the data structure it validates, making the code more organized and maintainable.
