Examples

Complete, production-ready examples demonstrating different use cases of confkit.

Basic Configuration

The simplest possible confkit setup — load typed config from environment variables.

package main

import (
    "log"
    "github.com/MimoJanra/confkit"
)

type Config struct {
    Port     int    `env:"PORT" default:"8080" validate:"min=1,max=65535"`
    Host     string `env:"HOST" default:"localhost"`
    Database string `env:"DATABASE_URL" validate:"required" secret:"true"`
}

func main() {
    cfg, err := confkit.Load[Config](confkit.FromEnv())
    if err != nil {
        log.Fatal(confkit.Explain(err))
    }

    log.Printf("Starting on %s:%d\n", cfg.Host, cfg.Port)
}
# Run
export DATABASE_URL="postgres://user:pass@localhost/db"
go run main.go

Multiple Sources with Precedence

Sources are checked in order — first source to provide a value wins. Environment variables override YAML, which overrides defaults.

type Config struct {
    Port     int    `yaml:"port" env:"PORT" default:"8080"`
    Database string `yaml:"database" env:"DATABASE_URL"`
    LogLevel string `yaml:"log_level" env:"LOG_LEVEL" default:"info"`
}

func main() {
    // Precedence: env vars override YAML override defaults
    cfg, err := confkit.Load[Config](
        confkit.FromYAML("config.yaml"),    // Base config
        confkit.FromEnv(),                  // Runtime overrides
    )
    if err != nil {
        log.Fatal(confkit.Explain(err))
    }
}
# config.yaml
port: 3000
database: postgres://localhost/mydb
log_level: debug

Nested Structures

Organize related settings into sub-structs. confkit recursively scans nested structs.

type DatabaseConfig struct {
    Host     string `yaml:"host" env:"DB_HOST" default:"localhost"`
    Port     int    `yaml:"port" env:"DB_PORT" default:"5432"`
    Username string `yaml:"user" env:"DB_USER"`
    Password string `yaml:"password" env:"DB_PASSWORD" secret:"true"`
}

type CacheConfig struct {
    Enabled bool `yaml:"enabled" env:"CACHE_ENABLED" default:"true"`
    TTL     int  `yaml:"ttl" env:"CACHE_TTL" default:"3600"`
}

type Config struct {
    DB       DatabaseConfig
    Cache    CacheConfig
    LogLevel string `env:"LOG_LEVEL" default:"info"`
}

func main() {
    cfg, err := confkit.Load[Config](
        confkit.FromYAML("config.yaml"),
        confkit.FromEnv(),
    )
    // Access nested: cfg.DB.Host, cfg.Cache.TTL
    _ = cfg; _ = err
}

Custom Validation

Register per-field validators with WithValidator. Reference them with validate:"custom=name".

type Config struct {
    AdminEmail string `env:"ADMIN_EMAIL" validate:"custom=email"`
}

func main() {
    cfg, err := confkit.LoadWithOptions[Config](
        confkit.WithSource(confkit.FromEnv()),
        confkit.WithValidator("email", func(val string) error {
            if !strings.Contains(val, "@") {
                return fmt.Errorf("must be a valid email address")
            }
            return nil
        },
    )
    _ = cfg; _ = err
}

Cross-Field Validation (Model Validators)

Validate relationships between fields — Pydantic-style model validators.

type TLSConfig struct {
    Enabled  bool   `env:"TLS_ENABLED"`
    CertPath string `env:"TLS_CERT_PATH"`
    KeyPath  string `env:"TLS_KEY_PATH"`
}

cfg, err := confkit.LoadWithOptions[TLSConfig](
    confkit.WithSource(confkit.FromEnv()),
    confkit.WithModelValidator(func(cfg *TLSConfig) error {
        if cfg.Enabled && cfg.CertPath == "" {
            return fmt.Errorf("tls_cert_path is required when tls_enabled is true")
        }
        if cfg.Enabled && cfg.KeyPath == "" {
            return fmt.Errorf("tls_key_path is required when tls_enabled is true")
        }
        return nil
    },
)

Format Validators

Built-in validators for common formats — no external libraries.

type Config struct {
    AdminEmail string `env:"ADMIN_EMAIL" validate:"required,email"`
    ServiceURL string `env:"SERVICE_URL" validate:"http_url"`
    ClientIP   string `env:"CLIENT_IP"   validate:"ip"`
    ServiceID  string `env:"SERVICE_ID"  validate:"uuid"`
    Port       int    `env:"PORT"        validate:"port"`
    APIKey     string `env:"API_KEY"     validate:"required,len=32,alphanum" secret:"true"`
    Region     string `env:"REGION"      validate:"regex=^[a-z]+-[a-z]+-[0-9]+$"`
}

Environment Variable Prefixes

Use prefix on a nested struct to automatically scope its environment variables.

type DatabaseConfig struct {
    Host string `env:"HOST" default:"localhost"`
    Port int    `env:"PORT" default:"5432"`
}

type Config struct {
    // Fields in this struct automatically get DB_ prefix
    DB DatabaseConfig `prefix:"DB_"`
}

func main() {
    // Reads from: DB_HOST, DB_PORT (not just HOST, PORT)
    cfg, err := confkit.Load[Config](confkit.FromEnv())
    _ = cfg; _ = err
}
DB_HOST=db.example.com
DB_PORT=3306

String Interpolation

Reference other fields in default values using ${FIELD_NAME} syntax.

type Config struct {
    AppName    string `env:"APP_NAME" default:"MyApp"`
    AppVersion string `env:"APP_VERSION" default:"1.0.0"`
    // Will expand to "MyApp v1.0.0"
    AppTitle   string `env:"APP_TITLE" default:"${APP_NAME} v${APP_VERSION}" `

    BaseURL  string `env:"BASE_URL" default:"https://api.example.com"`
    // Will expand to full URL
    UsersURL string `env:"USERS_URL" default:"${BASE_URL}/users"`
}

Hot Reload / File Watching

Use LoadWithWatcher to get live config updates when the file changes — without restarting your service.

func main() {
    cfg, watcher, err := confkit.LoadWithWatcher[Config](
        "config.yaml",
        confkit.FromEnv(),
        confkit.FromYAML("config.yaml"),
    )
    if err != nil {
        log.Fatal(err)
    }

    watcher.AddListener(func(oldCfg, newCfg any, err error) {
        if err != nil {
            log.Printf("Reload failed: %v", err)
            return
        }
        log.Printf("Config reloaded!\n")
        // Apply new config to running services
    })
    watcher.Start()

    startServer(cfg)
}

Secret Redaction

Mark fields with secret:"true" — confkit automatically redacts them in error messages, audit logs, and config dumps.

type Config struct {
    APIKey   string `env:"API_KEY" validate:"required" secret:"true"`
    Password string `env:"PASSWORD" validate:"required" secret:"true"`
    Username string `env:"USERNAME"`
}

func main() {
    cfg, err := confkit.Load[Config](confkit.FromEnv())
    if err != nil {
        // Secrets are automatically redacted
        fmt.Println(confkit.Explain(err))
        // Output:
        // APIKey
        //   error: field is required
        //   source: env (API_KEY=***)
    }
    _ = cfg
}

Audit Logging

See exactly which source provided each field value. Great for debugging config precedence issues.

cfg, err := confkit.LoadWithOptions[Config](
    confkit.WithSource(confkit.FromYAML("config.yaml")),
    confkit.WithSource(confkit.FromEnv()),
    confkit.WithAuditLogger(func(entries []confkit.AuditEntry) {
        for _, e := range entries {
            log.Printf("%-30s ← %s: %s", e.Field, e.Source, e.Value)
        }
    },
)
// Output:
// Server.Port                    ← env: 9000
// Server.Host                    ← yaml: 0.0.0.0
// Database.Password              ← env: <redacted>

Multi-File Config Merging

Merge base defaults with environment-specific overrides. First file wins for each key.

cfg, err := confkit.Load[Config](
    confkit.FromYAMLFiles(
        "config/base.yaml",        // shipped defaults
        "config/production.yaml",  // prod-specific values
        "config/local.yaml",       // developer overrides (git-ignored)
    ),
    confkit.FromEnv(), // highest priority
)
# base.yaml
port: 8080
log_level: info
db:
  host: localhost
  port: 5432

---

# production.yaml
log_level: warn
db:
  host: db.prod.internal

Result: db.host = db.prod.internal, db.port = 5432, log_level = warn.

Cloud Sources

Kubernetes ConfigMap

import "github.com/MimoJanra/confkit/k8s"

func main() {
    cfg, err := confkit.Load[Config](
        k8s.FromKubernetesConfigMap("default", "app-config"),
    )
    _ = cfg; _ = err
}
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  port: "8080"
  log_level: "info"

AWS Systems Manager Parameter Store

import "github.com/MimoJanra/confkit/aws"

func main() {
    cfg, err := confkit.Load[Config](
        aws.FromAWSSSMParameterStore("/prod/app/config"),
    )
    _ = cfg; _ = err
}

HashiCorp Vault

import (
    "os"
    "github.com/MimoJanra/confkit/vault"
)

func main() {
    auth := vault.VaultTokenAuth(os.Getenv("VAULT_TOKEN"))
    cfg, err := confkit.Load[Config](
        vault.FromVault("https://vault.example.com", auth, "/secret/myapp"),
    )
    _ = cfg; _ = err
}

AWS Secrets Manager

import "github.com/MimoJanra/confkit/aws"

func main() {
    cfg, err := confkit.Load[Config](
        aws.FromAWSSecretsManager("prod/app-secrets"),
    )
    _ = cfg; _ = err
}

Consul KV

import "github.com/MimoJanra/confkit/consul"

func main() {
    cfg, err := confkit.Load[Config](
        consul.FromConsulWithOptions("consul.example.com:8500", "token", "dc1"),
    )
    _ = cfg; _ = err
}

etcd

import "github.com/MimoJanra/confkit/etcd"

func main() {
    cfg, err := confkit.Load[Config](
        etcd.FromEtcdWithPrefix(
            []string{"etcd1.example.com:2379", "etcd2.example.com:2379"},
            "/myapp",
        ),
    )
    _ = cfg; _ = err
}

Observability

Prometheus Metrics

import "github.com/MimoJanra/confkit/prometheus"

m := prometheus.NewMetrics(prometheus.DefaultRegisterer)

cfg, err := confkit.LoadWithOptions[Config](
    confkit.WithSource(confkit.FromEnv()),
    m.Hook(),
)
// Tracks: confkit_loads_total, confkit_load_duration_seconds, confkit_errors_total

OpenTelemetry Tracing

import "github.com/MimoJanra/confkit/otel"

cfg, err := otel.Load[Config](ctx, tracer,
    confkit.FromYAML("config.yaml"),
    confkit.FromEnv(),
)
// Creates span "confkit.Load"
// Attributes: confkit.sources, confkit.success

Advanced: Custom Source

Implement the Source interface to load config from any backend.

type CustomSource struct {
    data map[string]string
}

func (s *CustomSource) Name() string { return "custom" }

func (s *CustomSource) Lookup(ctx context.Context, field *confkit.FieldInfo) (any, bool, error) {
    val, ok := s.data[field.Path]
    return val, ok, nil
}

func main() {
    source := &CustomSource{
        data: map[string]string{
            "Port":     "9000",
            "Database": "postgres://localhost/db",
        },
    }

    cfg, err := confkit.Load[Config](source)
    _ = cfg; _ = err
}

Full Production Setup

A complete production-grade configuration: nested structs, hot reload, audit logging, model validation.

package main

import (
    "log"
    "time"
    "github.com/MimoJanra/confkit"
)

type DatabaseConfig struct {
    Host     string        `yaml:"host" env:"DB_HOST" default:"localhost"`
    Port     int           `yaml:"port" env:"DB_PORT" default:"5432" validate:"min=1,max=65535"`
    Username string        `yaml:"username" env:"DB_USER" validate:"required"`
    Password string        `yaml:"password" env:"DB_PASSWORD" validate:"required" secret:"true"`
    SSL      bool          `yaml:"ssl" env:"DB_SSL" default:"true"`
    Timeout  time.Duration `yaml:"timeout" env:"DB_TIMEOUT" default:"30s"`
}

type ServerConfig struct {
    Host        string        `yaml:"host" env:"SERVER_HOST" default:"0.0.0.0"`
    Port        int           `yaml:"port" env:"SERVER_PORT" default:"8080" validate:"min=1,max=65535"`
    ReadTimeout time.Duration `yaml:"read_timeout" env:"SERVER_READ_TIMEOUT" default:"10s"`
}

type Config struct {
    Database    DatabaseConfig
    Server      ServerConfig
    LogLevel    string `yaml:"log_level" env:"LOG_LEVEL" validate:"oneof=debug,info,warn,error" default:"info"`
    Environment string `yaml:"env" env:"ENVIRONMENT" validate:"oneof=dev,staging,prod" default:"dev"`
}

func main() {
    cfg, watcher, err := confkit.LoadWithWatcher[Config](
        "config.yaml",
        confkit.FromYAML("config.yaml"),
        confkit.FromEnv(),
    )
    if err != nil {
        log.Fatal(confkit.Explain(err))
    }

    log.Printf("Running in %s mode\n", cfg.Environment)

    watcher.AddListener(func(_, newCfg any, err error) {
        if err != nil {
            log.Printf("Reload failed: %v\n", err)
            return
        }
        log.Printf("Config reloaded\n")
        updateServices(*newCfg.(*Config))
    })
    watcher.Start()

    startServer(cfg)
}

func updateServices(cfg Config) { /* apply new config to running services */ }

func startServer(cfg Config) {
    log.Printf("Listening on %s:%d\n", cfg.Server.Host, cfg.Server.Port)
}