Typed configuration loading and validation for Go services.
confkit defines the environment variables a service expects, validates them at
startup, assigns typed values into a struct, and can generate .env.example or
Markdown documentation from the same contract.
It is intentionally env-first. It does not try to be a full configuration system for every backend or file format.
go get github.com/ahmertsengol/confkitpackage main
import (
"log"
conf "github.com/ahmertsengol/confkit"
)
type Config struct {
Port int
DatabaseURL string
JWTSecret string
Env string
}
func main() {
cfg, err := conf.Load[Config](conf.Contract(
conf.Int("PORT").
Default(8080).
Min(1).
Max(65535).
Description("HTTP server port"),
conf.String("DATABASE_URL").
Required().
URL().
Description("Primary database connection URL"),
conf.String("JWT_SECRET").
Required().
Min(32).
Secret().
Description("JWT signing secret"),
conf.Enum("ENV", "development", "staging", "production").
Default("development"),
).Strict(), conf.WithDotEnv(".env"))
if err != nil {
log.Fatal(err)
}
log.Printf("starting %s service on port %d", cfg.Env, cfg.Port)
}Example error:
Invalid configuration:
DATABASE_URL
Required environment variable is missing.
PORT
Expected integer, got "abc".
JWT_SECRET
Must be at least 32 characters. Current value: [REDACTED].Load reads values in this order:
defaults < dotenv files < process environment < explicit sourcesWithDotEnv(".env")ignores a missing file.WithRequiredDotEnv(path)returns an error if the file cannot be read.WithSource(MapSource(...))is useful for tests and explicit overrides.- Secret fields are redacted in errors and generated examples.
- Contract errors, parse errors, and validation errors are returned together as
*confkit.Error. Contract(...).Strict()rejects exported struct fields that are not present in the contract.- Generated examples validate defaults and quote values when needed so the output can be read back by
confkit. - List defaults used for generated examples must not contain the list separator.
- Custom validators should be pure predicates; they may run during loading and documentation generation.
Dotenv support is intentionally minimal and meant for local development. It
supports common KEY=value, export KEY=value, quoted values, and inline
comments. For advanced dotenv behavior, load values with another package and
pass them through WithSource.
For small programs that only need typed env parsing:
type Config struct {
Port int `conf:"PORT"`
ServiceName string // SERVICE_NAME
Debug bool `conf:"DEBUG"`
}
cfg, err := conf.LoadEnv[Config]()LoadEnv does not apply contract validation, required fields, defaults, or
secret redaction. Fields without a conf tag use screaming snake case; tags are
only needed when the environment key should differ from the field name.
The contract can be used to generate .env.example:
_ = conf.WriteExample(contract, os.Stdout)# HTTP server port
PORT=8080
# Primary database connection URL
DATABASE_URL=
# JWT signing secret
JWT_SECRET=It can also generate Markdown documentation:
_ = conf.WriteMarkdown(contract, os.Stdout)| Key | Type | Required | Default | Secret | Description |
|---|---|---|---|---|---|
| PORT | int | no | 8080 | no | HTTP server port || Category | API |
|---|---|
| Field types | String, Int, Float, Bool, Duration, Enum, List |
| Contract options | Strict |
| Modifiers | Required, Optional, Default, Secret, Description, Desc |
| Validators | Min, Max, Regex, URL, Email, Hostname, IP, NonEmpty, Validate for typed custom rules, enum membership through Enum |
- Duplicate contract keys are rejected.
- Duplicate struct field mappings are rejected.
- Invalid regex validators return contract errors instead of panicking.
Strictmode can reject struct fields missing from the contract.conf:"-"skips a struct field.- Remote providers, hot reload, Vault, Consul, and Kubernetes integrations are outside the current package scope.