Framework
Configuration
The pkg/config package provides layered configuration loading: defaults, YAML file, and environment variables — in ascending priority order.
import "github.com/stanza-go/framework/pkg/config"
Loading configuration
From file and environment
cfg, err := config.Load("~/.stanza/config.yaml",
config.WithDefaults(map[string]string{
"server.addr": ":23710",
"log.level": "info",
}),
config.WithEnvPrefix("STANZA"),
config.WithRequired("auth.signing_key"),
)
if err != nil {
return err
}
if err := cfg.Validate(); err != nil {
return err // lists all missing required keys
}
Without a file
cfg := config.New(
config.WithDefaults(map[string]string{
"server.addr": ":8080",
}),
config.WithEnvPrefix("MYAPP"),
)
Resolution order
Values are resolved in this priority (highest wins):
- Environment variables —
STANZA_SERVER_ADDRoverridesserver.addr - YAML file values — from the config file
- Defaults — set via
WithDefaults
The environment variable name is derived from the key: uppercase, dots replaced with underscores, prefixed. For key server.addr with prefix STANZA, the env var is STANZA_SERVER_ADDR.
Reading values
Strings
addr := cfg.GetString("server.addr") // "" if not found
addr := cfg.GetStringOr("server.addr", ":8080") // fallback if empty
Numbers
port := cfg.GetInt("server.port") // 0 if missing or invalid
size := cfg.GetInt64("max.upload.size") // 0 if missing or invalid
rate := cfg.GetFloat64("throttle.rate") // 0.0 if missing or invalid
Booleans
debug := cfg.GetBool("debug")
// Truthy: "true", "1", "yes", "on" (case-insensitive)
// Everything else: false
Durations
timeout := cfg.GetDuration("server.timeout")
// Parses Go duration strings: "30s", "5m", "1h30m"
// Returns 0 if missing or invalid
Check existence
if cfg.Has("smtp.host") {
// key has a non-empty value from any source
}
Validation
Mark keys as required, then call Validate:
cfg, err := config.Load(path,
config.WithRequired("auth.signing_key", "db.path"),
)
if err := cfg.Validate(); err != nil {
// error message lists all missing required keys
log.Fatal(err)
}
Validate checks that all required keys have non-empty values after resolving defaults, file, and environment.
YAML file format
The config file uses flat dot-notation keys in YAML:
server:
addr: ":23710"
log:
level: "debug"
auth:
signing_key: "your-64-char-hex-key-here"
secure_cookies: false
cors:
origins: "http://localhost:23706,http://localhost:23700"
If the YAML file does not exist, it is silently skipped — the app works with defaults and environment variables only. This is intentional: the file is optional, the environment is always available.
In a Stanza app
The standalone app loads config from the data directory:
func provideConfig(dir *datadir.Dir) *config.Config {
cfg, _ := config.Load(dir.Path("config.yaml"),
config.WithDefaults(map[string]string{
"server.addr": ":23710",
"log.level": "info",
"cors.origins": "http://localhost:23706,http://localhost:23700",
}),
config.WithEnvPrefix("STANZA"),
)
return cfg
}
Override any setting with environment variables in production:
STANZA_SERVER_ADDR=:8080
STANZA_LOG_LEVEL=warn
STANZA_AUTH_SIGNING_KEY=<64-char-hex>
DATA_DIR=/data