go-config-tree is a lightweight, dependency-free Go package designed to manage environment variables and configuration files for any Go project.
It provides a clean workflow for configuration management:
- Define your own schema with normal Go structs.
- Initialize the struct with your hardcoded default values.
- Use
.envfiles andos.Getenv()environment variables to dynamically override defaults. - Strictly enforce required variables in production.
- Zero Dependencies: A highly lightweight package with no external dependencies.
.envFile Support: Includes a built-in, lightweight.envparser.- Required Fields: Enforce required environment variables using the
requiredtag option. - Hierarchical Structs: Map values seamlessly to nested Go structs using
envstruct tags. - Type Conversions: Automatically converts environment variables into your struct field types (string, int, bool, float).
go get github.com/MajAhd/go-config-treeCreate your Go structs exactly the way you want your config tree structured. Use env tags for environment variable bindings, and append ,required to fields that must be present.
package main
import (
"fmt"
"log"
"os"
configtree "github.com/MajAhd/go-config-tree"
)
type Config struct {
App struct {
Name string `env:"APP_NAME,required"`
Port int `env:"APP_PORT,required"`
Debug bool `env:"APP_DEBUG"`
}
DB struct {
Host string `env:"DB_HOST,required"`
Username string `env:"DB_USER"`
Password string `env:"DB_PASSWORD,required"`
}
}
func main() {
// 1. Set your hardcoded default values
cfg := Config{}
cfg.App.Name = "my-default-app"
cfg.App.Port = 3000
cfg.App.Debug = false
cfg.DB.Host = "localhost"
cfg.DB.Username = "root"
// (Simulate an OS-level environment variable like you would set in docker/production)
os.Setenv("DB_PASSWORD", "supersecret123")
// 2. Load the configuration
// This will read the `.env` file (if it exists) and merge all environment
// variables over the defaults inside `cfg`.
if err := configtree.Load(&cfg, ".env"); err != nil {
log.Fatalf("Error loading config: %v", err)
}
fmt.Printf("Loaded Config:\n")
fmt.Printf("App Name: %s\n", cfg.App.Name)
fmt.Printf("App Port: %d\n", cfg.App.Port)
fmt.Printf("DB Host: %s\n", cfg.DB.Host)
fmt.Printf("DB Password: %s\n", cfg.DB.Password)
}In the root of your project, you can drop a .env file (or .env.local) to easily switch environments:
# .env
APP_PORT=8080
APP_DEBUG=true
DB_HOST="127.0.0.1"
DB_USER=adminWhen Load is called, go-config-tree performs the following merges over your pre-populated defaults:
- Applies variables found in the
.envfile (if provided). - Applies system environment variables (like those from
os.Setenvor Docker).
Output:
Loaded Config:
App Name: my-default-app
App Port: 8080
DB Host: 127.0.0.1
DB Password: supersecret123
If you omit the hardcoded defaults and pass a blank struct into Load(), go-config-tree will strictly enforce that all fields marked with ,required must exist in the environment variables.
If an environment variable is missing, Load() will return an error:
required environment variable DB_PASSWORD is missing and no default value was provided
This is extremely powerful for ensuring production environments don't start up with missing configurations.
We highly recommend separating your configurations into a dedicated config/ directory for scalable applications:
config/configSchema.go- Holds your configuration struct.config/default.go- Contains your baseline, hardcoded values and returns a populated*Schema.config/local.go- Uses theDefault()baseline, and loads.env.localover it for local development overrides.config/prod.go- BypassesDefault()and initializes an empty&Schema{}. It then callsLoad()without passing an.envfile, meaning it strictly pulls fromos.Getenv()(like from Kubernetes Secrets) and strictly validates allrequiredtags!
See the examples/basic folder for a fully functional implementation of this best-practice architecture.