go / env
I use small helper functions to read environment variables with type safety and default values.
The pattern
package env
import (
"os"
"strconv"
"strings"
"time"
)
func String(key, defaultValue string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultValue
}
func Bool(key string, defaultValue bool) bool {
if v := os.Getenv(key); v != "" {
b, err := strconv.ParseBool(v)
if err != nil {
return defaultValue
}
return b
}
return defaultValue
}
func Int(key string, defaultValue int) int {
if v := os.Getenv(key); v != "" {
i, err := strconv.Atoi(v)
if err != nil {
return defaultValue
}
return i
}
return defaultValue
}
func Duration(key string, defaultValue time.Duration) time.Duration {
if v := os.Getenv(key); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
return defaultValue
}
return d
}
return defaultValue
}
func Slice(key string, defaultValue []string) []string {
if v := os.Getenv(key); v != "" {
return strings.Split(v, ",")
}
return defaultValue
}
Usage
Define config at package level:
var (
port = env.Int("PORT", 8080)
debug = env.Bool("DEBUG", false)
timeout = env.Duration("TIMEOUT", 30*time.Second)
allowedIPs = env.Slice("ALLOWED_IPS", []string{"127.0.0.1"})
databaseURL = env.String("DATABASE_URL", "postgres://localhost/dev")
)
func main() {
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
Benefits
- Type safety. No
strconvscattered through application code. - Defaults inline. Easy to see what happens when env vars are missing.
- Fail-safe. Parse errors fall back to defaults instead of crashing.
Variations
If you prefer to fail on parse errors:
func MustInt(key string, defaultValue int) int {
if v := os.Getenv(key); v != "" {
i, err := strconv.Atoi(v)
if err != nil {
panic(fmt.Sprintf("%s: %v", key, err))
}
return i
}
return defaultValue
}
If you need required env vars with no default:
func Require(key string) string {
if v := os.Getenv(key); v != "" {
return v
}
panic(fmt.Sprintf("missing required env var: %s", key))
}
Comparison with flag-style
Some packages use a flag-like pattern with pointers and Parse():
var port = env.Int("PORT", 8080)
func main() {
env.Parse() // must call before using *port
fmt.Println(*port)
}
I prefer direct values over pointers. Simpler to use, one less thing to forget.