This guide describes the recommended configuration pattern for modkit apps using the core modkit/config package design.
If your project has not adopted modkit/config yet, use this guide as the migration target from ad-hoc os.Getenv calls.
- Keep configuration explicit and testable.
- Parse once, then inject typed values via DI.
- Fail with contextual typed errors on missing/invalid required values.
- Preserve normal module visibility rules for config tokens.
Use a dedicated config module that exports typed tokens.
package app
import (
"time"
"github.com/go-modkit/modkit/modkit/config"
"github.com/go-modkit/modkit/modkit/module"
)
const (
TokenHTTPAddr module.Token = "config.http_addr"
TokenJWTTTL module.Token = "config.jwt_ttl"
)
func NewConfigModule() module.Module {
defaultAddr := ":8080"
defaultTTL := 1 * time.Hour
return config.NewModule(
config.WithTyped(TokenHTTPAddr, config.ValueSpec[string]{
Key: "HTTP_ADDR",
Default: &defaultAddr,
Parse: config.ParseString,
Required: false,
}, true),
config.WithTyped(TokenJWTTTL, config.ValueSpec[time.Duration]{
Key: "JWT_TTL",
Default: &defaultTTL,
Parse: config.ParseDuration,
Required: false,
}, true),
)
}When an app needs multiple independent config modules, set distinct names with config.WithModuleName("...") to avoid duplicate module names in the graph.
Resolve config in providers/controllers using module.Get[T]:
module.ProviderDef{
Token: "auth.service",
Build: func(r module.Resolver) (any, error) {
ttl, err := module.Get[time.Duration](r, TokenJWTTTL)
if err != nil {
return nil, err
}
return NewAuthService(ttl), nil
},
}Use Required: true for values that must exist, and Sensitive: true for secret-bearing keys:
secretSpec := config.ValueSpec[string]{
Key: "JWT_SECRET",
Required: true,
Sensitive: true,
Parse: config.ParseString,
}Behavior:
- Missing required values return
MissingRequiredError. - Parse failures return
ParseErrorwith key/type context. - Sensitive values are never included in diagnostic value surfaces.
Use errors.Is with sentinels for category checks:
config.ErrMissingRequiredconfig.ErrParseconfig.ErrInvalidSpec
Use errors.As when you need structured fields (Key, Token, Type, Sensitive).
The core helpers cover common env value types:
ParseStringParseIntParseFloat64ParseBoolParseDuration(Gotime.ParseDurationformat)ParseCSV(comma-separated, trimmed, empty entries removed)
By default, empty-after-trim values are treated as unset.
AllowEmpty: false(default): empty values follow required/default behavior.AllowEmpty: true: empty values are treated as explicitly set and passed toParse.
Config tokens behave exactly like any other provider token.
- Export only the tokens importers need.
- Keep internal/raw configuration private.
- If a token is not exported, importers receive
TokenNotVisibleError.
- Use
t.Setenvfor key-by-key deterministic setup. - Test default, missing, empty, whitespace, and invalid parse cases.
- Assert typed errors with
errors.As. - Verify secret-bearing keys do not leak raw values in errors.
For existing apps that use os.Getenv directly:
- Keep existing
Load()surface to avoid breaking callers. - Move parsing logic into
modkit/configspecs and helpers. - Export typed tokens and update consumers gradually.
- Remove duplicated env parsing utilities once parity is verified.