EVO provides a thread-safe, multi-source configuration system via lib/settings. Settings are loaded from environment variables, YAML files, databases, and command-line arguments, with a clear priority order.
- Command-line arguments (
--KEY=value) - YAML configuration file (
./config.ymlor path from-cflag) - Database settings (if database is enabled)
- Environment variables
import "github.com/getevo/evo/v2/lib/settings"
// Read a value (with optional default)
host := settings.Get("DATABASE.HOST", "localhost").String()
port := settings.Get("DATABASE.PORT", 5432).Int()
debug := settings.Get("APP.DEBUG", false).Bool()
// Set a value at runtime
settings.Set("APP.NAME", "MyService")
// Check existence
exists, value := settings.Has("FEATURE.ENABLED")EVO calls settings.Init() automatically during evo.Setup().
The default config file is ./config.yml. Override with -c /path/to/config.yml.
# config.yml
HTTP:
Host: "0.0.0.0"
Port: "8080"
Database:
Enabled: true
Type: postgres
Server: "localhost:5432"
Username: "postgres"
Password: "secret"
Database: "myapp"
Schema: "public"
SSLMode: "disable"
Debug: 3
MaxOpenConns: "100"
MaxIdleConns: "10"
ConnMaxLifTime: "1h"
SlowQueryThreshold: "500ms"
App:
Name: "MyApp"
Version: "1.0.0"
Debug: "false"Keys are case-insensitive and dot-notation is supported. DATABASE.HOST, database.host, and Database.Host all refer to the same key internally.
Any environment variable is available as a setting. Dots and hyphens are treated as separators:
export DATABASE_HOST=localhost
export DATABASE_PORT=5432
export APP_NAME=MyServicesettings.Get("DATABASE.HOST").String() // "localhost"
settings.Get("APP.NAME").String() // "MyService"Override any setting at startup:
./myapp --DATABASE.HOST=prod-db --DATABASE.PORT=5432 --APP.DEBUG=trueSelect a custom config file:
./myapp -c /etc/myapp/config.ymlRetrieves a setting. Returns generic.Value which converts to any type.
// String
name := settings.Get("APP.NAME", "default").String()
// Int
port := settings.Get("HTTP.PORT", 8080).Int()
// Bool
debug := settings.Get("APP.DEBUG", false).Bool()
// Duration
timeout := settings.Get("HTTP.TIMEOUT", "30s").Duration()
// Float
ratio := settings.Get("CACHE.RATIO", 0.75).Float64()Checks if a key exists.
exists, value := settings.Has("FEATURE.ENABLED")
if exists {
fmt.Println("Feature flag:", value.Bool())
}Updates a setting at runtime. Persists to database if database settings are enabled. Triggers registered change callbacks.
settings.Set("APP.MAINTENANCE", true)
settings.Set("CACHE.TTL", "5m")
settings.Set("MAX.RETRIES", 3)Updates multiple settings efficiently in a single lock cycle.
settings.SetMulti(map[string]any{
"DATABASE.HOST": "new-host",
"DATABASE.PORT": 5433,
"APP.DEBUG": false,
})Returns a snapshot of all current settings.
all := settings.All()
for key, value := range all {
fmt.Printf("%s = %v\n", key, value)
}Removes a setting. Returns true if it existed.
removed := settings.Delete("TEMP.VALUE")Saves all current settings to a YAML file.
settings.Set("DATABASE.HOST", "localhost")
settings.Set("DATABASE.PORT", 5432)
settings.SaveToYAML("./config.yml")
// Result:
// database:
// host: localhost
// port: 5432Saves all settings to the database (requires database to be enabled).
if err := settings.SaveToDB(); err != nil {
log.Error("failed to save settings", "error", err)
}Hot-reloads all settings from all sources and triggers OnReload callbacks. Useful for configuration changes without restart.
if err := settings.Reload(); err != nil {
log.Error("reload failed", "error", err)
}Called whenever settings.Reload() runs (also called at startup).
settings.OnReload(func() {
log.Info("configuration reloaded")
cache.Clear()
})Called when a matching setting changes. Supports wildcards.
// Exact key
settings.Track("DATABASE.HOST", func() {
log.Info("database host changed — reconnecting...")
reconnectDB()
})
// Wildcard: any DATABASE.* key
settings.Track("DATABASE.*", func() {
log.Info("database config changed")
reconnectDB()
})
// Global wildcard: any setting
settings.Track("*", func() {
log.Info("configuration changed")
app.Reload()
})Note: The callback receives no parameters. Read the new value inside the callback:
settings.Track("HTTP.PORT", func() {
newPort := settings.Get("HTTP.PORT").Int()
log.Info("port changed", "port", newPort)
})The callback is also invoked once immediately at registration time.
All keys are normalized before storage:
- Converted to uppercase
- Non-alphanumeric characters replaced with underscore
"database.host" → "DATABASE_HOST"
"HTTP-Port" → "HTTP_PORT"
"app/name" → "APP_NAME"
Use any separator in your code — it all maps to the same key:
settings.Get("database.host")
settings.Get("DATABASE_HOST")
settings.Get("database-host")
// All return the same valueWhen the database is enabled, settings are loaded from and saved to a settings table. The table is created automatically.
// After db is enabled and evo.Setup() is called:
// Persist a setting to the database
settings.Set("FEATURE.DARK_MODE", true) // auto-saved to DB
// Read a setting that was changed in the database
settings.Reload() // reloads all sources including DB
value := settings.Get("FEATURE.DARK_MODE").Bool()settings.Get returns a generic.Value. Available conversion methods:
v := settings.Get("MY.KEY", "default")
v.String() // string
v.Int() // int
v.Int64() // int64
v.Float64() // float64
v.Bool() // bool
v.Duration() // time.Duration (parses "30s", "5m", "1h")
v.Time() // time.Time
v.Bytes() // []byte
v.IsNil() // boolimport (
"github.com/getevo/evo/v2/lib/settings"
"github.com/getevo/evo/v2/lib/pgsql"
"github.com/getevo/evo/v2"
)
func main() {
evo.Setup(pgsql.Driver{})
// Settings are loaded — DATABASE.* keys available
host := settings.Get("DATABASE.SERVER").String()
db := settings.Get("DATABASE.DATABASE").String()
log.Info("connected", "host", host, "db", db)
evo.Run()
}// In config.yml:
// Feature:
// NewUI: "true"
// Beta: "false"
enabled := settings.Get("FEATURE.NEWUI", false).Bool()
if enabled {
evo.Get("/dashboard", newDashboardHandler)
}settings.Track("DATABASE.*", func() {
log.Info("database config changed — reconnecting")
// trigger reconnection logic
})settings.Track is the recommended pattern for keeping app-level variables in sync with configuration. The callback fires once at registration (to initialize the value) and again whenever the setting changes:
import (
"github.com/getevo/evo/v2/lib/settings"
openai "github.com/sashabaranov/go-openai"
)
var (
APIKey string
client *openai.Client
)
func (a App) Register() error {
settings.Track("OPENAI.API_KEY", func() {
APIKey = settings.Get("OPENAI.API_KEY").String()
client = openai.NewClient(APIKey)
})
return nil
}When OPENAI.API_KEY changes at runtime (e.g. via settings.Set or a config reload), the callback re-runs and client is replaced automatically — no restart required.
required := []string{"DATABASE.SERVER", "DATABASE.USERNAME", "DATABASE.PASSWORD", "APP.SECRET"}
for _, key := range required {
if exists, _ := settings.Has(key); !exists {
log.Fatal("missing required config: " + key)
}
}// Load, override, save
settings.Set("HTTP.PORT", "9090")
settings.Set("APP.DEBUG", "true")
settings.SaveToYAML("./config.yml")