Typed config and secrets loader for Go services using Uber FX.
Replaces manual os.Getenv calls and raw Vault HTTP requests with struct-tag-driven, FX-native config/secrets injection.
go get github.com/dehwyy/configfxVault subpackage:
go get github.com/dehwyy/configfx/vault// internal/config/config.go
package config
type Config struct {
AppEnv string `env:"APP_ENV,default=local"`
AuthURL string `env:"AUTH_URL,required"`
OtelEndpoint string `env:"OTEL_ENDPOINT,default=localhost:4317"`
CORSOrigins []string `env:"CORS_ORIGINS,default=https://example.com"`
}
func (c *Config) IsLocal() bool { return c.AppEnv == "local" }
// internal/config/secrets.go
type Secrets struct {
PgConnFmt string `vault:"kv.shared.pg.conn.dev"`
NatsServers string `vault:"kv.shared.nats.dev.servers"`
NatsSeedKey string `vault:"kv.shared.nats.seedKey"`
}func main() {
fx.New(
configfx.FxModule[config.Config](),
cfgvault.FxModule[config.Secrets](
os.Getenv("KEY_VAULT_ADDRESS"),
os.Getenv("KEY_VAULT_TOKEN"),
),
// both *config.Config and *config.Secrets are now in the DI container
fx.Provide(func(cfg *config.Config, sec *config.Secrets) (*nats.Conn, error) {
// ...
}),
).Run()
}type Opts struct {
fx.In
Config *config.Config
Secrets *config.Secrets
}
func New(opts Opts) *Service { ... }env:"KEY" // read APP_KEY, zero value if not set
env:"KEY,default=VALUE" // use VALUE if not set
env:"KEY,required" // error if not set and no default
Supported field types: string, int, bool, []string (comma-separated).
vault:"mount.path.field"
mountβ KV v1 mount name (e.g.kv)pathβ secret path within mount (e.g.shared)fieldβ field name inside the secret map (e.g.pg.conn.dev)
Tag vault:"kv.shared.pg.conn.dev" β reads GET /v1/kv/shared, takes data["pg.conn.dev"].
Batch reads: all fields from the same (mount, path) share one HTTP request.
If you need to load outside FX (e.g. early init, tests):
cfg, err := configfx.Load[config.Config]()
sec, err := vault.Load[config.Secrets](vaultAddr, vaultToken)The check subpackage provides a diagnostic binary that validates both env vars and Vault keys before service start.
// cmd/check/main.go
package main
import (
"os"
"git.example.com/myservice/internal/config"
"github.com/dehwyy/configfx/check"
)
func main() {
check.Run[config.Config, config.Secrets](
os.Getenv("KEY_VAULT_ADDRESS"),
os.Getenv("KEY_VAULT_TOKEN"),
)
}Output example:
Config validation (env vars):
β APP_ENV = "local"
β AUTH_URL = "https://auth.example.com/api/v1"
β REQUIRED_KEY missing required env var
Secrets validation (Vault kv://https://vault.example.com):
β kv.shared.pg.conn.dev β PgConnFmt
β kv.shared.nats.dev.servers failed to read kv/shared: ...
1 error(s) found. Fix before starting the service.
Exits 0 on success, 1 on any error.
configfx/
βββ loader.go # Load[T]() β env vars β struct
βββ validate.go # Validate[T]() β dry-run, no side effects
βββ fx.go # FxModule[T]() β wraps Load in fx.Provide
βββ internal/
β βββ env/ # tag parser + type coercion
β βββ field/ # reflect-based field setter
βββ vault/
β βββ loader.go # Load[T](addr, token) β Vault KV v1 β struct
β βββ validate.go # Validate[T](addr, token) β dry-run
β βββ fx.go # FxModule[T](addr, token) β wraps Load in fx.Provide
β βββ internal/ # vault tag parser
βββ check/
βββ check.go # Run[C, S](addr, token) β CLI validator
- Go 1.25.5+
- Vault KV v1 (not KV v2)