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

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.

← All articles