If you’re tired of writing the same env var parsing bullshit over and over, gonfiguration handles all the annoying shit for you. It’s got reflection-based mapping, supports all the types you actually use, and doesn’t crash when someone fucks up the config.
Here’s How It Works
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/psyb0t/gonfiguration"
)
type AppConfig struct {
// Basic configuration
AppName string `env:"APP_NAME"`
Debug bool `env:"DEBUG"`
Port int `env:"PORT"`
// String slices for lists
AllowedHosts []string `env:"ALLOWED_HOSTS"`
Features []string `env:"FEATURES"`
// Time duration fields
Timeout time.Duration `env:"TIMEOUT"`
RetryDelay time.Duration `env:"RETRY_DELAY"`
// Numeric types
MaxUsers int64 `env:"MAX_USERS"`
CacheSize uint32 `env:"CACHE_SIZE"`
LoadFactor float64 `env:"LOAD_FACTOR"`
// Database configuration
DBDSN string `env:"DB_DSN"`
DBName string `env:"DB_NAME"`
DBUser string `env:"DB_USER"`
DBPass string `env:"DB_PASS"`
}
func main() {
cfg := AppConfig{}
// Set sensible defaults
gonfiguration.SetDefaults(map[string]interface{}{
"APP_NAME": "MyAwesomeApp",
"DEBUG": false,
"PORT": 8080,
"ALLOWED_HOSTS": []string{"localhost", "127.0.0.1", "*.example.com"},
"FEATURES": []string{"auth", "logging", "metrics"},
"TIMEOUT": 30 * time.Second,
"RETRY_DELAY": 5 * time.Second,
"MAX_USERS": 1000,
"CACHE_SIZE": 256,
"LOAD_FACTOR": 0.75,
"DB_DSN": "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable",
})
// Set some env vars (normally these come from your environment)
os.Setenv("APP_NAME", "ProductionBeast")
os.Setenv("DEBUG", "true")
os.Setenv("ALLOWED_HOSTS", "api.myapp.com, cdn.myapp.com , admin.myapp.com")
os.Setenv("FEATURES", "auth,logging,metrics,analytics,caching")
os.Setenv("TIMEOUT", "1m30s")
os.Setenv("MAX_USERS", "50000")
os.Setenv("DB_NAME", "production_db")
os.Setenv("DB_USER", "app_user")
os.Setenv("DB_PASS", "super-secret-password")
// Parse it
if err := gonfiguration.Parse(&cfg); err != nil {
log.Fatalf("Config parsing fucked up: %v", err)
}
// Check what we got
fmt.Printf("🚀 App: %s (Debug: %v)\n", cfg.AppName, cfg.Debug)
fmt.Printf("🌐 Listening on port: %d\n", cfg.Port)
fmt.Printf("🏠 Allowed hosts: %v\n", cfg.AllowedHosts)
fmt.Printf("⭐ Features enabled: %v\n", cfg.Features)
fmt.Printf("⏱️ Timeout: %v, Retry delay: %v\n", cfg.Timeout, cfg.RetryDelay)
fmt.Printf("👥 Max users: %d, Cache size: %d MB\n", cfg.MaxUsers, cfg.CacheSize)
fmt.Printf("📊 Load factor: %.2f\n", cfg.LoadFactor)
fmt.Printf("🗄️ Database: %s@%s\n", cfg.DBUser, cfg.DBName)
}
Output:
🚀 App: ProductionBeast (Debug: true)
🌐 Listening on port: 8080
🏠 Allowed hosts: [api.myapp.com cdn.myapp.com admin.myapp.com]
⭐ Features enabled: [auth logging metrics analytics caching]
⏱️ Timeout: 1m30s, Retry delay: 5s
👥 Max users: 50000, Cache size: 256 MB
📊 Load factor: 0.75
🗄️ Database: app_user@production_db
Installation
go get github.com/psyb0t/gonfiguration
That’s it. Minimal deps, no bullshit setup.
How It Works
1. Define Your Config Struct
Tag your struct fields with env:"VAR_NAME"
and use whatever types you need:
type UltimateConfig struct {
// Basic types
AppName string `env:"APP_NAME"`
Debug bool `env:"DEBUG"`
// Integer types
Count int `env:"COUNT"`
SmallNum int8 `env:"SMALL_NUM"`
BigNum int64 `env:"BIG_NUM"`
// Unsigned integers
UserID uint32 `env:"USER_ID"`
FileSize uint64 `env:"FILE_SIZE"`
// Floating point numbers
Ratio float32 `env:"RATIO"`
Precision float64 `env:"PRECISION"`
// Time durations
Timeout time.Duration `env:"TIMEOUT"`
Interval time.Duration `env:"INTERVAL"`
// String slices
Tags []string `env:"TAGS"`
Servers []string `env:"SERVERS"`
}
2. Set Defaults
Don’t let your app crash because someone forgot an env var:
gonfiguration.SetDefaults(map[string]interface{}{
"APP_NAME": "MyApp",
"DEBUG": false,
"COUNT": 100,
"RATIO": 0.8,
"TIMEOUT": 30*time.Second,
"TAGS": []string{"prod", "api", "web"},
"SERVERS": []string{"srv1.com", "srv2.com", "srv3.com"},
})
3. Parse It
cfg := UltimateConfig{}
if err := gonfiguration.Parse(&cfg); err != nil {
log.Fatalf("Config parsing shit the bed: %v", err)
}
4. Done
Your config is loaded. Use it however you want.
String Slices Actually Work
Set a comma-separated env var and get a proper []string
:
type Config struct {
Microservices []string `env:"MICROSERVICES"`
APIKeys []string `env:"API_KEYS"`
}
os.Setenv("MICROSERVICES", "auth-service, user-service , payment-service, notification-service")
os.Setenv("API_KEYS", "key1,key2,key3")
cfg := Config{}
gonfiguration.Parse(&cfg)
// You get:
// cfg.Microservices = ["auth-service", "user-service", "payment-service", "notification-service"]
// cfg.APIKeys = ["key1", "key2", "key3"]
Empty strings become empty slices:
os.Setenv("OPTIONAL_FEATURES", "")
// Results in: cfg.OptionalFeatures = []string{}
Time Durations Don’t Suck
Use Go’s duration strings and get actual time.Duration
values:
type TimingConfig struct {
HTTPTimeout time.Duration `env:"HTTP_TIMEOUT"`
RetryInterval time.Duration `env:"RETRY_INTERVAL"`
CacheExpiry time.Duration `env:"CACHE_EXPIRY"`
}
os.Setenv("HTTP_TIMEOUT", "30s")
os.Setenv("RETRY_INTERVAL", "5m30s")
os.Setenv("CACHE_EXPIRY", "24h")
cfg := TimingConfig{}
gonfiguration.Parse(&cfg)
// You get actual durations:
// cfg.HTTPTimeout = 30 * time.Second
// cfg.RetryInterval = 5*time.Minute + 30*time.Second
// cfg.CacheExpiry = 24 * time.Hour
Debugging Your Config
Get all the values if you need to debug something:
allValues := gonfiguration.GetAllValues()
fmt.Printf("All config values: %+v\n", allValues)
defaults := gonfiguration.GetDefaults()
fmt.Printf("Default values: %+v\n", defaults)
envVars := gonfiguration.GetEnvVars()
fmt.Printf("Environment variables: %+v\n", envVars)
// Reset everything if needed
gonfiguration.Reset()
It’s thread-safe, so concurrent access won’t fuck things up:
// Won't break
go func() {
gonfiguration.SetDefault("KEY1", "value1")
}()
go func() {
cfg := MyConfig{}
gonfiguration.Parse(&cfg)
}()
When Things Go Wrong
You get actual error messages instead of cryptic bullshit:
// Forgot the pointer?
err := gonfiguration.Parse(cfg) // Should be &cfg
// Error: "yo, the destination ain't a pointer"
// Wrong type in env var?
os.Setenv("PORT", "not-a-number")
err := gonfiguration.Parse(&cfg)
// Error: "wtf.. Failed to parse int: strconv.ParseInt: parsing \"not-a-number\": invalid syntax"
Real Example
Web service config that doesn’t suck:
type WebServiceConfig struct {
// Server configuration
ListenAddress string `env:"LISTEN_ADDRESS"`
Port int `env:"PORT"`
ReadTimeout time.Duration `env:"READ_TIMEOUT"`
WriteTimeout time.Duration `env:"WRITE_TIMEOUT"`
// Security
AllowedOrigins []string `env:"ALLOWED_ORIGINS"`
APIKeys []string `env:"API_KEYS"`
RateLimitRPS int `env:"RATE_LIMIT_RPS"`
// Database
DBHost string `env:"DB_HOST"`
DBPort int `env:"DB_PORT"`
DBUser string `env:"DB_USER"`
DBPassword string `env:"DB_PASSWORD"`
DBName string `env:"DB_NAME"`
// Redis cache
RedisURL string `env:"REDIS_URL"`
CacheExpiry time.Duration `env:"CACHE_EXPIRY"`
// Logging
LogLevel string `env:"LOG_LEVEL"`
LogOutputs []string `env:"LOG_OUTPUTS"`
// Feature flags
EnableMetrics bool `env:"ENABLE_METRICS"`
EnableTracing bool `env:"ENABLE_TRACING"`
}
func main() {
cfg := WebServiceConfig{}
gonfiguration.SetDefaults(map[string]interface{}{
"LISTEN_ADDRESS": "0.0.0.0",
"PORT": 8080,
"READ_TIMEOUT": 30*time.Second,
"WRITE_TIMEOUT": 30*time.Second,
"ALLOWED_ORIGINS": []string{"https://myapp.com", "https://admin.myapp.com"},
"RATE_LIMIT_RPS": 100,
"DB_HOST": "localhost",
"DB_PORT": 5432,
"CACHE_EXPIRY": 1*time.Hour,
"LOG_LEVEL": "info",
"LOG_OUTPUTS": []string{"stdout", "file"},
"ENABLE_METRICS": true,
"ENABLE_TRACING": false,
})
if err := gonfiguration.Parse(&cfg); err != nil {
log.Fatalf("Config fucked up: %v", err)
}
// Go build your app
startWebService(cfg)
}
That’s It
If you’re still manually parsing environment variables in 2025, use this instead.
GitHub: github.com/psyb0t/gonfiguration
Post updated: 11 Sept 2025