go-config
A Spring Boot style configuration loader for Go.
Automatically discovers configuration files, supports multiple formats, profiles, environment variable binding, remote configuration centers (etcd, Nacos, Consul, Apollo), and live reload.
Features
- 🧩 Zero configuration – Just place
config.yml in config/ or the current directory.
- 🌍 Multiple profiles – Set
APP_PROFILE=dev to load config-dev.yml.
- 📁 Multi-format – YAML, TOML, JSON, XML, INI, Properties.
- 🔄 Env override –
SERVER_PORT=9090 automatically overrides server.port.
- ⚡ Live reload – File watcher and remote config center push support.
- 🔌 Extensible – Custom parsers and custom configuration sources via simple interfaces.
- 🎯 Type-safe access –
config.Get[int]("server.port") or struct binding.
Comparison with Other Libraries
| Feature |
go-config |
Viper |
koanf |
configor |
| Auto file discovery |
✅ |
❌ |
❌ |
✅ (limited) |
| Profile support (dev/prod) |
✅ |
❌ |
❌ |
❌ |
| Env variable auto-binding |
✅ |
✅ (manual bind) |
✅ (manual) |
✅ |
| Live reload |
✅ |
❌ (requires extra) |
❌ (requires extra) |
❌ |
| Remote config (etcd / Nacos / Consul / Apollo) |
✅ (built-in) |
✅ (via providers) |
✅ (via providers) |
❌ |
Type‑safe Get[T] |
✅ |
❌ |
❌ |
❌ |
| Multiple format support |
✅ (6 formats) |
✅ (5 formats) |
✅ (6+ formats) |
✅ (5 formats) |
| Zero‑code config center push |
✅ |
❌ |
❌ |
❌ |
| Simple API |
✅ |
⚠️ moderate |
⚠️ moderate |
✅ |
- ✅ = Built‑in, out of the box.
- ⚠️ = Possible but requires manual setup.
- ❌ = Not supported natively.
Why choose go-config?
It's the only Go library that combines Spring‑Boot‑style conventions, auto‑discovery, profiles, live reload, and built‑in support for etcd/Nacos — with a one‑liner API: config.LoadAndWatch(&cfg).
Installation
go get github.com/Junsy2025/go-config
Quick Start
- Create config/config.yml:
server:
port: 8080
- Load and use in your code:
package main
import (
"fmt"
"log"
"github.com/Junsy2025/go-config"
)
type Config struct {
Server struct {
Port int `mapstructure:"port"`
} `mapstructure:"server"`
}
func main() {
var cfg Config
if err := config.Load(&cfg); err != nil {
log.Fatal(err)
}
fmt.Println(cfg.Server.Port)
}
Profiles
Set the APP_PROFILE environment variable to switch profiles. The loader will first look for config-{profile}.{ext}, then fall back to config.{ext}.
Environment Variable Binding
All environment variables are automatically mapped to configuration keys.
SERVER_PORT becomes server.port and overrides file values.
Priority order: YAML → TOML → JSON → XML → INI → Properties
Custom Options
config.Load(&cfg,
config.WithConfigDir("/etc/myapp"), // specify config directory
config.WithProfile("prod"), // active profile
config.WithParser(myCustomParser), // register custom parser
)
Live Reload
Watch local file changes and automatically re-bind to your struct:
config.LoadAndWatch(&cfg)
// Optional callback on any update
config.OnChange(func(i interface{}) {
c := i.(*Config)
log.Println("Config updated, new port:", c.Server.Port)
})
To stop watching:
defer config.Stop()
Remote Configuration Sources
go-config provides built-in support for etcd, Nacos, Consul, and Apollo.
All sources implement the same interface, so you can mix them freely — remote sources take precedence over the local file source (they are merged, and remote keys override local ones).
Every remote source requires:
-
a parser that matches the format of the configuration stored in the remote backend (e.g. &parser.YAMLParser{}, &parser.JSONParser{}).
-
to be added via config.WithSource(source).
-
the use of config.LoadAndWatch(&cfg, ...) to enable live reload.
etcd
import (
"github.com/Junsy2025/go-config"
"github.com/Junsy2025/go-config/parser"
"github.com/Junsy2025/go-config/remote"
)
etcdSrc, err := remote.NewEtcdSource(
[]string{"127.0.0.1:2379"}, // etcd endpoints
"/config/myapp.yaml", // key path
&parser.YAMLParser{}, // value format
)
if err != nil {
log.Fatal(err)
}
var cfg AppConfig
if err := config.LoadAndWatch(&cfg, config.WithSource(etcdSrc)); err != nil {
log.Fatal(err)
}
// Optional: react to updates
config.OnChange(func(i interface{}) {
c := i.(*AppConfig)
log.Println("Config changed:", c.Server.Port)
})
Nacos
nacosSrc, err := remote.NewNacosSource(
"127.0.0.1:8848", // server address
"", // namespace ID (empty = public)
"myapp.yaml", // Data ID
"DEFAULT_GROUP", // Group
&parser.YAMLParser{}, // value format
)
if err != nil {
log.Fatal(err)
}
config.LoadAndWatch(&cfg, config.WithSource(nacosSrc))
Consul
consulSrc, err := remote.NewConsulSource(
"127.0.0.1:8500", // agent address
"config/myapp.yaml", // KV path
&parser.YAMLParser{}, // value format
)
if err != nil {
log.Fatal(err)
}
config.LoadAndWatch(&cfg, config.WithSource(consulSrc))
Apollo
apolloSrc, err := remote.NewApolloSource(
"myApp", // AppId
"default", // Cluster
"application", // Namespace (e.g., "application", "myapp.yaml")
"http://localhost:8080", // Meta server URL
&parser.YAMLParser{}, // value format
)
if err != nil {
log.Fatal(err)
}
config.LoadAndWatch(&cfg, config.WithSource(apolloSrc))
Custom Source
Implement the loader.Source interface to connect any backend:
type Source interface {
Load(ctx context.Context) (map[string]interface{}, error)
Watch(ctx context.Context, onChange func(map[string]interface{})) error
Name() string
}
// Usage
config.LoadAndWatch(&cfg, config.WithSource(myCustomSource))
Type-Safe Global Access
You can also read values without passing a struct:
port := config.Get[int]("server.port")
dbHost := config.Get[string]("database.host")
This reads directly from the live configuration (including updates).