AntConfig is a small, zero-dependency Go configuration library focused on simplicity, clarity, and predictable precedence. Configuration is defined through tagged structs, which can be overridden by environment variables, a .env file, or command-line flags. Optional configuration files are supported in JSON or JSONC format only. Unlike many other configuration libraries that include support for TOML, YAML, and extensive feature sets, AntConfig is opinionated: it keeps things minimal, simple, and free of external dependencies.
- Opinionated, zero-dependency design keeps binaries small and secure.
- Works out of the box with tagged structs, env vars, JSON/JSONC files, and CLI flags.
- Automatic config discovery and
.envloading streamline deployment workflows. - Type-safe parsing for core Go types avoids reflection surprises at runtime.
- Zero dependencies: uses only the Go standard library.
- JSON and JSONC: helpers to strip comments and trailing commas for JSONC.
- Tag-based configuration:
default:"…"andenv:"ENV_NAME"on struct fields. - Nested structs supported: including pointer fields (auto-initialized when needed).
- Type-safe env parsing: string, int/uint, bool, float64, and
[]intfrom JSON. - Supports .env files
- Discovery helpers: locate config file by walking upward from CWD or executable.
- Bootstrapping new Go microservices with minimal config wiring.
- Shipping CLIs where predictable flag and env precedence is critical.
- Migrating from heavier configuration stacks (e.g., Viper) to a lighter runtime.
- Embedding configuration into serverless functions or containers with strict size budgets.
Current precedence when applying configuration values:
- Defaults from struct tags (
default:"…") - Configuration file (.json or .jsonc). If no path is set via
SetConfigPath, AntConfig auto-discoversconfig.jsoncorconfig.jsonstarting from the current working directory and walking upward. - .env file (when
SetEnvPathis used) - Environment variables (
env:"NAME") — override .env - Command line flags (
flag:"name") — highest priority
Install via go get (Go modules):
go get github.com/robfordww/antconfig@latestpackage main
import (
"flag"
"fmt"
"os"
"github.com/robfordww/antconfig"
)
type SecretKey struct {
Key string `default:"secretkey123" env:"SEC" flag:"sec" desc:"Secret key for encryption"`
}
type Config struct {
Host string `default:"localhost" env:"CONFIG_HOST"`
Port int `default:"8080" env:"CONFIG_PORT"`
SC SecretKey
}
func main() {
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
var cfg Config
ac := antconfig.New().MustSetConfig(&cfg)
ac.SetFlagPrefix("config-") // optional flag prefix
ac.MustBindConfigFlags(fs) // register flags from struct tags
// Optional: add your own app flags
fs.Bool("verbose", false, "Enable verbose output")
// Show env help after flag defaults
fs.Usage = func() {
fs.PrintDefaults()
fmt.Print("\n" + ac.EnvHelpString())
}
_ = fs.Parse(os.Args[1:]) // parse CLI args
// Apply: defaults -> config file -> .env -> env -> flags
if err := ac.WriteConfigValues(); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("Config: %#v\n", cfg)
}Behavior shown above is verified in tests: defaults are set first, then env vars (if non-empty) override them. Empty env values do not override defaults.
The jsonc.go helper lets you read JSONC (comments + trailing commas) and
turn it into strict JSON before unmarshaling.
data, err := os.ReadFile("config_test.jsonc")
if err != nil { /* handle */ }
jsonBytes := antconfig.ToJSON(data) // or ToJSONInPlace(data)
if err := json.Unmarshal(jsonBytes, &cfg); err != nil { /* handle */ }An example JSONC file is included at config_test.jsonc.
Two helpers return a config file path by walking parent directories up to a limit (10 levels):
antconfig.LocateFromWorkingDir(filename)antconfig.LocateFromExe(filename)
Both return the first match travering upwards from the directory, otherwise ErrConfigNotFound is returned.
-
type AntConfig(fields unexported)SetEnvPath(path string) error: set.EnvPathand validate the file exists. When set,.envis loaded and variables are added to the process environment only if they are not already set. IfEnvPathis not set, AntConfig auto-discovers a.envin the current working directory.SetConfigPath(path string) error: set.ConfigPathand validate it exists.WriteConfigValues() error: apply defaults, config file (JSON/JSONC), .env, env, then flag overrides to the config passed viaSetConfig.SetFlagArgs(args []string): provide explicit CLI args (defaults toos.Args[1:]).SetFlagPrefix(prefix string): set optional prefix used for generated CLI flags.ListFlags(cfg any) ([]FlagSpec, error): return available flags with names and types.SetConfig(&cfg) error: provide the config pointer for reflection when binding flags.MustSetConfig(&cfg) *AntConfig: likeSetConfigbut panics on error and returns the receiver for chaining.BindConfigFlags(fs *flag.FlagSet) error: register flags derived from your config onto a providedFlagSet(and bind it for later reads).
-
Struct tags on
cfgfieldsdefault:"…": default value used when field is zero-value.env:"ENV_NAME": if present and non-empty, overrides the field with a parsed value.flag:"name": if present, allows--name value(or--name=value) to override the field. WhenSetFlagPrefix("config-")is set, use--config-nameinstead.desc:"…": optional description used as usage text when registering flags viaBindConfigFlagsand shown in env help.
You can build CLI usage dynamically from your config struct. For example:
var cfg AppConfig
ant := antconfig.New().MustSetConfig(&cfg)
ant.SetFlagPrefix("config-") // optional
flags, _ := ant.ListFlags(&cfg)
fmt.Println("Config flags:")
for _, f := range flags {
fmt.Printf(" --%s (%s)\n", f.CLI, f.Kind)
}
// Populate a FlagSet and parse
fs := flag.NewFlagSet("myapp", flag.ExitOnError)
if err := ant.BindConfigFlags(fs); err != nil { panic(err) }
_ = fs.Parse(os.Args[1:])
// Apply: defaults -> config file -> .env -> env -> flags (from FlagSet)
if err := ant.WriteConfigValues(); err != nil { panic(err) }- Nested structs and pointers to structs are traversed and initialized as needed.
- Empty env values do not override defaults.
A small playground command included under cmd/. Use it as a experimental testingground.
Build and run:
go build -o antapp ./playground
./antapp -config-host=localhostRun all tests:
go test ./...The tests exercise defaults, env overrides, pointer initialization for nested structs, discovery helpers, and JSONC parsing.
Feel free to open an issue or PR if you have any suggestions.
