Configure an app
gas/config is the one module that is not a service. It is built and loaded
before the app, so a bad configuration fails before anything else starts,
and every other module binds its own settings through the
gas.ConfigProvider you register.
go get github.com/gasmod/gas/configLoading
Section titled “Loading”// Config is built and loaded before the app, so a bad configuration fails// before anything else starts. Later providers override earlier ones.func load() *config.Config { cfg := config.New( config.WithProvider(providers.NewEnvProvider()), // lowest priority config.WithProvider(providers.NewJSONProvider( // overrides env providers.WithJSONFilePath("config.json"), )), config.WithExtension(gasenv.NewExtension()), )
if err := cfg.Load(); err != nil { log.Fatal(err) } return cfg}// Registered as an instance, so every module can bind its own settings.func register(cfg *config.Config) gas.Option { return gas.WithServiceInstance[gas.ConfigProvider](cfg)}Later providers override earlier ones, so the order above means environment variables are the base and the JSON file wins. Pick the order that matches how you deploy; a common one is env last so a container can override anything.
Key naming
Section titled “Key naming”Keys are lowercased. By default they stay flat, so DATABASE_HOST becomes
database_host. Use a double underscore to nest, which is what every Gas module
expects:
DATABASE__DSN=postgres://localhost/app # database.dsnSTORAGE__BUCKET=uploads # storage.bucketJWT__SIGNING_KEY=... # jwt.signing_keyA prefix is stripped literally, so include the trailing separator:
WithEnvPrefix("APP_") turns APP_HOST into host, while WithEnvPrefix("APP")
leaves you with _host.
// Environment variables are lowercased and flat by default. Use a double// underscore to nest: DATABASE__HOST becomes database.host.func prefixed() *providers.EnvProvider { return providers.NewEnvProvider( // The prefix is stripped literally, so include the trailing separator. providers.WithEnvPrefix("APP_"), providers.WithEnvSeparator("__"), )}Providers
Section titled “Providers”| Provider | Source |
|---|---|
providers.NewEnvProvider() |
Process environment |
providers.NewJSONProvider() |
A JSON file, from disk or an fs.FS |
providers.NewDotEnvProvider() |
A .env file |
secretsmanager.NewProvider() |
AWS Secrets Manager, fetched eagerly at load |
Write your own by implementing Name() string and Load() (map[string]any, error).
Add LoadContext(ctx) and LoadWithContext will pass its context through, which
matters for anything doing network calls.
Binding to structs
Section titled “Binding to structs”// Bind maps configuration into a struct, matching on json tags first and then// case-insensitive field names, and validates it on the way through.type AppConfig struct { gasenv.WithGasEnv
Database struct { Host string `json:"host"` Port int `json:"port" validate:"required"` } `json:"database"`
RequestTimeout time.Duration `json:"request_timeout"`}
func bind(cfg *config.Config) (*AppConfig, error) { var out AppConfig if err := cfg.Bind(&out); err != nil { return nil, err } return &out, nil}Fields match on json tags first, then case-insensitively by name. Supported:
every int, uint, and float width, bool, string, time.Duration, slices,
fixed-size arrays, maps, nested structs, and embedded structs. Comma-separated
strings bind to slices; a fixed-size array needs a real list, and a source with
more elements than fit is an error rather than a silent truncation.
Validation runs through go-playground/validator
on validate tags. Pass config.WithValidate(false) to skip it, or
config.WithValidator(v) to supply an instance with your own custom rules
registered.
Environments
Section titled “Environments”The gasenv extension resolves the current environment from config providers,
then GAS_ENV, then a default of Development. Embed gasenv.WithGasEnv in
your config struct and Bind populates it, giving you IsProduction(),
IsDevelopmentLike(), and friends. Gas uses this itself, which is how gas/ui
knows to rebuild templates on every request in development.
Server settings
Section titled “Server settings”Core reads its own gas.Config for the HTTP server.
// The server settings Gas itself reads. DefaultConfig fills these in; override// only what you need.func serverConfig() *gas.Config { c := gas.DefaultConfig() c.Server.Port = 9090 c.Server.ShutdownTimeout = 30 * time.Second return c}| Field | Default |
|---|---|
Server.Host |
0.0.0.0 |
Server.Port |
8080 |
Server.ReadTimeout |
5s |
Server.WriteTimeout |
10s |
Server.IdleTimeout |
2m |
Server.ShutdownTimeout |
30s |
Testing
Section titled “Testing”configtest.MockConfig records calls and takes per-method Fn overrides. When
a test needs real Get/Find/Bind behaviour over known values, use
configtest.NewMockConfigWithValues, which delegates to a real config and
avoids leaking the machine’s environment into the test. Seed it with nested
maps, not dotted keys: values go through SetDefaults, which does not split on
., so "database.host" would become one flat key that Get("database.host")
cannot reach.