Slim

env - Environment Variable Management Library

env is a simple and easy-to-use environment variable management library that provides .env file loading, type conversion, grouped queries, and other features. It adopts the “initialize once, read many times” design philosophy, suitable for high-concurrency scenarios.

Installation

go get -u go-slim.dev/env

Quick Start

Basic Usage

package mainimport (    "log"    "go-slim.dev/env")func main() {    // Initialize: load .env file    if err := env.Init(); err != nil {        log.Fatal(err)    }    // Lock environment variables to prevent runtime modifications    env.Lock()    // Read environment variables    port := env.Int("PORT", 8080)    debug := env.Bool("DEBUG", false)    dbHost := env.String("DB_HOST", "localhost")    log.Printf("Server running on port %d", port)}

.env File Example

# Application configurationAPP_ENV=developmentAPP_NAME=MyAppPORT=8080DEBUG=true# Database configurationDB_HOST=localhostDB_PORT=5432DB_NAME=mydbDB_USER=adminDB_PASSWORD=secret# Cache configurationCACHE_DRIVER=redisCACHE_DATABASE=1CACHE_SCOPE=app:

Core Functions

1. Initialization Functions

Init - Initialize Environment Variables

func Init(root ...string) error

Description:

Loads the .env file from the runtime directory and initializes environment variables. This is the entry point for the entire library.

Parameters:

  • root: Optional parameter specifying the directory containing the .env file; if not specified, uses the current program’s runtime directory

Loading Order:

  1. System environment variables
  2. .env file
  3. .env.local file (higher priority, can override .env)
  4. .env.{APP_ENV} file (based on APP_ENV variable)
  5. .env.{APP_ENV}.local file (highest priority)

Examples:

// Use current directoryif err := env.Init(); err != nil {    log.Fatal(err)}// Specify configuration directoryif err := env.Init("/path/to/config"); err != nil {    log.Fatal(err)}// Multi-environment configuration example// .env.development, .env.production are automatically loaded based on APP_ENV

Notes:

  • This function is not thread-safe and must be called single-threaded during program startup
  • Later loaded files will override variables with the same name from earlier loaded files
  • Non-existent files will not cause errors; only read failures will return errors

InitWithDir - Initialize from Specified Directory

func InitWithDir(dir string) error

Description:

Loads the .env file from the specified directory. The difference from Init() is that a directory parameter must be provided.

Parameters:

  • dir: Absolute or relative path to the directory containing the .env file

Example:

// Load from specified directoryif err := env.InitWithDir("/etc/myapp"); err != nil {    log.Fatal(err)}

Load - Load Specified File

func Load(filenames ...string) error

Description:

Loads specified environment variable files. Can be used to load additional configuration files.

Parameters:

  • filenames: List of file paths to load

Example:

// Load additional configurations after initializationenv.Init()env.Load("/path/to/secrets.env")env.Load("/path/to/custom1.env", "/path/to/custom2.env")

Notes:

  • Cannot be called after Lock()
  • Will return an error if files do not exist

Lock - Lock Environment Variables

func Lock()

Description:

Locks global environment variables to prevent subsequent modifications. Any write operations (Init, Load, Updates) will fail after calling.

Best Practice:

Call Lock() immediately after initialization to ensure only reading environment variables at runtime.

Example:

func main() {    // Initialization phase    if err := env.Init(); err != nil {        log.Fatal(err)    }    // Lock to prevent runtime modifications    env.Lock()    // Runtime phase - only reading allowed    go worker1()    go worker2()}

Characteristics:

  • Locking is one-way and cannot be unlocked
  • Calling Load() after locking returns ErrLocked error
  • Calling Updates() or Clean() after locking causes panic
  • Can be called repeatedly (idempotent operation)

2. Query Functions

Lookup - Query Environment Variable

func Lookup(name string) (string, bool)

Description:

Queries the value of a specified environment variable.

Parameters:

  • name: Environment variable name

Return Values:

  • First return value: Variable’s string value
  • Second return value: true if variable exists and value is non-empty, false if doesn’t exist or is empty

Example:

if value, exists := env.Lookup("API_KEY"); exists {    fmt.Println("API Key:", value)} else {    fmt.Println("API Key not set")}

Exists - Check if Variable Exists

func Exists(name string) bool

Description:

Checks if an environment variable exists. Unlike Lookup, returns true as long as the variable is defined, even if the value is empty.

Example:

if env.Exists("DEBUG") {    // DEBUG variable is defined (may be empty)}// Compare with Lookupvalue, exists := env.Lookup("DEBUG")// exists is only true when DEBUG exists and is non-empty

String - Get String Value

func String(name string, fallback ...string) string

Description:

Returns the string value of the specified environment variable. If it doesn’t exist or is empty, returns the default value.

Parameters:

  • name: Environment variable name
  • fallback: Optional default value (variadic parameter, only uses the first one)

Example:

// No default value, returns empty string if doesn't existhost := env.String("DB_HOST")// With default valuehost := env.String("DB_HOST", "localhost")name := env.String("APP_NAME", "MyApp")

Int - Get Integer Value

func Int(name string, fallback ...int) int

Description:

Returns the integer value of the specified environment variable. If it doesn’t exist, is empty, or conversion fails, returns the default value.

Example:

// No default value, returns 0 on failureport := env.Int("PORT")// With default valueport := env.Int("PORT", 8080)maxConn := env.Int("MAX_CONNECTIONS", 100)

Float - Get Float Value

func Float(name string, fallback ...float64) float64

Description:

Returns the float value of the specified environment variable. If it doesn’t exist, is empty, or conversion fails, returns the default value.

Example:

rate := env.Float("TAX_RATE", 0.08)ratio := env.Float("COMPRESSION_RATIO", 1.0)

Bool - Get Boolean Value

func Bool(name string, fallback ...bool) bool

Description:

Returns the boolean value of the specified environment variable. Supported values:

  • true: “1”, “t”, “T”, “true”, “TRUE”, “True”
  • false: “0”, “f”, “F”, “false”, “FALSE”, “False”

Example:

debug := env.Bool("DEBUG", false)verbose := env.Bool("VERBOSE", true)

Duration - Get Time Duration Value

func Duration(name string, fallback ...time.Duration) time.Duration

Description:

Returns the time duration value of the specified environment variable. Supports Go standard time format (like “1h30m”) or integers (treated as nanoseconds).

Example:

// .env file// TIMEOUT=30s// INTERVAL=5mtimeout := env.Duration("TIMEOUT", 10*time.Second)interval := env.Duration("INTERVAL", 1*time.Minute)

Bytes - Get Byte Slice Value

func Bytes(name string, fallback ...[]byte) []byte

Description:

Returns the byte slice value of the specified environment variable.

Example:

data := env.Bytes("BINARY_DATA")key := env.Bytes("SECRET_KEY", []byte("default-key"))

List - Get String List

func List(name string, fallback ...[]string) []string

Description:

Returns a string list from the specified environment variable. Values are split by commas, and each element is automatically trimmed.

Example:

// .env file// ALLOWED_ORIGINS=http://localhost:3000,https://example.com,https://api.example.com// TAGS=go, web, apiorigins := env.List("ALLOWED_ORIGINS")// []string{"http://localhost:3000", "https://example.com", "https://api.example.com"}tags := env.List("TAGS")// []string{"go", "web", "api"} (automatically trimmed)// Using default valuehosts := env.List("REDIS_HOSTS", []string{"localhost:6379"})

3. Batch Query Functions

Map - Get All Variables with Same Prefix

func Map(prefix string) map[string]string

Description:

Aggregates environment variables with the same prefix into a map. The returned keys have the prefix removed.

Example:

// .env file// DB_HOST=localhost// DB_PORT=5432// DB_NAME=mydb// DB_USER=admindbConfig := env.Map("DB_")// map[string]string{//     "HOST": "localhost",//     "PORT": "5432",//     "NAME": "mydb",//     "USER": "admin",// }

Where - Custom Filter Query

func Where(filter func(name, value string) bool) map[string]string

Description:

Returns environment variables filtered by a custom filter function.

Example:

// Get all variables starting with API_apiVars := env.Where(func(name, value string) bool {    return strings.HasPrefix(name, "API_")})// Get all variables with value "true"trueVars := env.Where(func(name, value string) bool {    return value == "true"})// Get all non-empty variablesnonEmpty := env.Where(func(name, value string) bool {    return value != ""})

All - Get All Environment Variables

func All() map[string]string

Description:

Returns all environment variables (including system environment variables and variables from .env files).

Example:

allVars := env.All()for name, value := range allVars {    fmt.Printf("%s=%s\n", name, value)}

4. Structure Filling

Fill - Fill Structure

func Fill(structure any) error

Description:

Automatically fills structure fields using environment variables. Specify the corresponding environment variable name via the env tag.

Example:

// .env file// APP_NAME=MyApp// APP_PORT=8080// APP_DEBUG=true// DB_HOST=localhost// DB_PORT=5432type Config struct {    AppName  string `env:"APP_NAME"`    Port     int    `env:"APP_PORT"`    Debug    bool   `env:"APP_DEBUG"`    Database struct {        Host string `env:"DB_HOST"`        Port int    `env:"DB_PORT"`    }}var cfg Configif err := env.Fill(&cfg); err != nil {    log.Fatal(err)}fmt.Println(cfg.AppName)  // MyAppfmt.Println(cfg.Port)     // 8080fmt.Println(cfg.Debug)    // true

Supported Types:

  • Basic types: string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool
  • Time types: time.Time, time.Duration
  • Slice types: []int, []string, etc.
  • Nested structures

Notes:

  • Must pass a structure pointer
  • Nested structures are recursively filled
  • Fields must be exported (start with uppercase letter)
  • Unsupported types will return an error

5. Grouped Query - Signed

Signed - Create Grouped Querier

func Signed(prefix, category string) Signer

Description:

Creates a grouped querier for operating on environment variables with the same prefix but needing to distinguish different scenarios.

Query Strategy:

Uses two-level lookup with fallback mechanism:

  1. First lookup: PREFIX_CATEGORY_KEY
  2. If not found, fallback to: PREFIX_KEY

This allows defining common configurations at the prefix level and overriding specific configurations at the category level.

Example:

// .env file// CACHE_DRIVER=redis// CACHE_DATABASE=1// CACHE_SCOPE=app:// CACHE_BOOK_DATABASE=10// CACHE_BOOK_SCOPE=app:books:// CACHE_USER_DATABASE=11// CACHE_USER_SCOPE=app:users:// Get BOOK category cache configurationbookCache := env.Signed("CACHE", "BOOK")bookCache.String("DRIVER")    // "redis" (fallback to CACHE_DRIVER)bookCache.Int("DATABASE")     // 10 (from CACHE_BOOK_DATABASE)bookCache.String("SCOPE")     // "app:books:" (from CACHE_BOOK_SCOPE)// Get USER category cache configurationuserCache := env.Signed("CACHE", "USER")userCache.String("DRIVER")    // "redis" (fallback to CACHE_DRIVER)userCache.Int("DATABASE")     // 11 (from CACHE_USER_DATABASE)userCache.String("SCOPE")     // "app:users:" (from CACHE_USER_SCOPE)// Get default cache configuration (no category specified)defaultCache := env.Signed("CACHE", "")defaultCache.String("DRIVER") // "redis"defaultCache.Int("DATABASE")  // 1defaultCache.String("SCOPE")  // "app:"

Signer Interface Methods:

The Signer interface provides the same query methods as global functions:

  • Lookup(key string) (string, bool)
  • Exists(key string) bool
  • String(key string, fallback ...string) string
  • Bytes(key string, fallback ...[]byte) []byte
  • Int(key string, fallback ...int) int
  • Duration(key string, fallback ...time.Duration) time.Duration
  • Bool(key string, fallback ...bool) bool
  • List(key string, fallback ...[]string) []string
  • Map(prefix string) map[string]string
  • Where(filter func(name, value string) bool) map[string]string
  • Fill(structure any) error

Use Cases:

Suitable for configuring similar but different environment variables for different modules, environments, or tenants:

// Multi-tenant configurationtenant1DB := env.Signed("DB", "TENANT1")tenant2DB := env.Signed("DB", "TENANT2")// Multi-service configurationauthService := env.Signed("SERVICE", "AUTH")apiService := env.Signed("SERVICE", "API")// Multi-environment cacheprodCache := env.Signed("CACHE", "PROD")devCache := env.Signed("CACHE", "DEV")

6. Helper Functions

Path - Get Path Relative to Initialization Directory

func Path(path ...string) string

Description:

Returns an absolute path based on the initialization directory.

Example:

// Assume initialization directory is /appenv.Init("/app")env.Path()                    // "/app"env.Path("config")            // "/app/config"env.Path("logs", "app.log")   // "/app/logs/app.log"

Is - Check Application Environment

func Is(env ...string) bool

Description:

Checks if the APP_ENV environment variable matches any of the given values.

Example:

// .env file// APP_ENV=developmentif env.Is("development", "dev") {    // Development environment    log.Println("Running in development mode")}if env.Is("production", "prod") {    // Production environment} else {    // Non-production environment}

Updates - Update Environment Variables

func Updates(data map[string]string) bool

Description:

Batch updates environment variables. Returns true for success, false for failure (e.g., already locked).

Example:

// Dynamically set environment variablessuccess := env.Updates(map[string]string{    "FEATURE_FLAG_A": "true",    "FEATURE_FLAG_B": "false",})if !success {    log.Println("Failed to update environment")}

Notes:

  • Cannot be used after calling Lock()
  • Not thread-safe, only use during initialization phase

Use Cases

1. Multi-Environment Configuration

// Project structure// .env                  # Default configuration// .env.local            # Local override (not committed to git)// .env.development      # Development environment// .env.development.local// .env.production       # Production environment// .env.production.local// main.gofunc main() {    // Automatically loads corresponding configuration based on APP_ENV    if err := env.Init(); err != nil {        log.Fatal(err)    }    env.Lock()    // Execute different logic based on environment    if env.Is("development") {        // Development environment configuration        gin.SetMode(gin.DebugMode)    } else if env.Is("production") {        // Production environment configuration        gin.SetMode(gin.ReleaseMode)    }}

2. Database Configuration Management

type DatabaseConfig struct {    Host     string `env:"DB_HOST"`    Port     int    `env:"DB_PORT"`    Database string `env:"DB_NAME"`    Username string `env:"DB_USER"`    Password string `env:"DB_PASSWORD"`    MaxConns int    `env:"DB_MAX_CONNS"`    SSLMode  string `env:"DB_SSL_MODE"`}func NewDatabaseConfig() (*DatabaseConfig, error) {    cfg := &DatabaseConfig{}    if err := env.Fill(cfg); err != nil {        return nil, err    }    return cfg, nil}func main() {    env.Init()    env.Lock()    dbCfg, err := NewDatabaseConfig()    if err != nil {        log.Fatal(err)    }    dsn := fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=%s",        dbCfg.Host, dbCfg.Port, dbCfg.Database,        dbCfg.Username, dbCfg.Password, dbCfg.SSLMode)}

3. Multi-Tenant/Multi-Instance Configuration

// .env file// REDIS_HOST=localhost// REDIS_PORT=6379// REDIS_SESSION_DATABASE=0// REDIS_SESSION_PREFIX=session:// REDIS_CACHE_DATABASE=1// REDIS_CACHE_PREFIX=cache:// REDIS_QUEUE_DATABASE=2// REDIS_QUEUE_PREFIX=queue:// Create different Redis clients for different purposesfunc setupRedis() {    env.Init()    env.Lock()    // Session Redis    sessionRedis := env.Signed("REDIS", "SESSION")    sessionClient := redis.NewClient(&redis.Options{        Addr: fmt.Sprintf("%s:%d",            sessionRedis.String("HOST", "localhost"),            sessionRedis.Int("PORT", 6379)),        DB: sessionRedis.Int("DATABASE", 0),    })    // Cache Redis    cacheRedis := env.Signed("REDIS", "CACHE")    cacheClient := redis.NewClient(&redis.Options{        Addr: fmt.Sprintf("%s:%d",            cacheRedis.String("HOST", "localhost"),            cacheRedis.Int("PORT", 6379)),        DB: cacheRedis.Int("DATABASE", 1),    })    // Queue Redis    queueRedis := env.Signed("REDIS", "QUEUE")    queueClient := redis.NewClient(&redis.Options{        Addr: fmt.Sprintf("%s:%d",            queueRedis.String("HOST", "localhost"),            queueRedis.Int("PORT", 6379)),        DB: queueRedis.Int("DATABASE", 2),    })}

4. Feature Flags

// .env file// FEATURE_NEW_UI=true// FEATURE_BETA_API=false// FEATURE_ANALYTICS=truetype Features struct {    NewUI      bool `env:"FEATURE_NEW_UI"`    BetaAPI    bool `env:"FEATURE_BETA_API"`    Analytics  bool `env:"FEATURE_ANALYTICS"`}func main() {    env.Init()    env.Lock()    features := &Features{}    env.Fill(features)    if features.NewUI {        // Enable new UI    }    if features.BetaAPI {        // Enable Beta API    }}

5. Service Discovery Configuration

// .env file// SERVICES=auth,api,notification,payment// SERVICE_AUTH_URL=http://auth:8001// SERVICE_API_URL=http://api:8002// SERVICE_NOTIFICATION_URL=http://notification:8003// SERVICE_PAYMENT_URL=http://payment:8004func loadServiceURLs() map[string]string {    env.Init()    env.Lock()    services := env.List("SERVICES")    urls := make(map[string]string)    for _, service := range services {        signer := env.Signed("SERVICE", strings.ToUpper(service))        if url, exists := signer.Lookup("URL"); exists {            urls[service] = url        }    }    return urls}// UsageserviceURLs := loadServiceURLs()authURL := serviceURLs["auth"]        // http://auth:8001apiURL := serviceURLs["api"]          // http://api:8002

Thread Safety Notes

Design Model: “Initialize Once, Read Many Times”

The env library adopts a clear two-phase design:

Initialization Phase (single-threaded):

  • Call Init(), InitWithDir(), Load(), Updates()
  • Must complete before starting any goroutines
  • These functions are not thread-safe

Runtime Phase (multi-threaded):

  • Call Lookup(), String(), Int(), etc., read functions
  • All read operations are thread-safe
  • Multiple goroutines can concurrently read

Correct Usage

func main() {    // ✓ Correct: Single-threaded initialization at startup    if err := env.Init(); err != nil {        log.Fatal(err)    }    // ✓ Best practice: Lock to prevent accidental modifications    env.Lock()    // ✓ Correct: Read operations are thread-safe    go func() {        cache := env.Signed("CACHE", "BOOK")        driver := cache.String("DRIVER")  // Safe    }()    go func() {        port := env.Int("PORT", 8080)     // Safe    }()}

Incorrect Usage

func main() {    // ✗ Wrong: Concurrent calls to Init will cause data races    go env.Init()  // Don't do this!    go env.Init()  // Don't do this!    // ✗ Wrong: Modifying environment variables at runtime will cause data races    go func() {        env.Load(".env.runtime")  // Don't do this at runtime!    }()}

Why No Locks Are Needed?

  • Initialization Phase: Single-threaded execution, no need for locks
  • Runtime Phase: Read-only access, Go’s slice reading is inherently thread-safe
  • Environment Data: Never changes after initialization completes

The Lock() function’s purpose is to enforce the read-only convention at runtime, preventing accidental write operations.


API Reference

Global Functions

FunctionDescriptionThread-Safe
Init(root ...string) errorInitialize env varsNo
InitWithDir(dir string) errorInitialize from dirNo
Load(filenames ...string) errorLoad specified filesNo
Lock()Lock env varsYes
Signed(prefix, category string) SignerCreate grouped querierYes
Path(path ...string) stringGet relative pathYes
Is(env ...string) boolCheck app environmentYes
Updates(data map[string]string) boolUpdate env varsNo

Query Functions (Thread-Safe)

FunctionDescription
Lookup(name string) (string, bool)Query env var
Exists(name string) boolCheck existence
String(name string, fallback ...string) stringGet string value
Bytes(name string, fallback ...[]byte) []byteGet byte slice
Int(name string, fallback ...int) intGet integer value
Float(name string, fallback ...float64) float64Get float value
Bool(name string, fallback ...bool) boolGet boolean value
Duration(name string, fallback ...time.Duration) time.DurationGet time duration
List(name string, fallback ...[]string) []stringGet string list
Map(prefix string) map[string]stringGet vars with same prefix
Where(filter func(name, value string) bool) map[string]stringCustom filter query
Fill(structure any) errorFill structure
All() map[string]stringGet all env vars

Signer Interface

Signer provides the same query methods as global functions but automatically adds prefix and category.


Notes

1. Initialization Order

The loading order of environment variables affects the final values (later loaded overrides earlier loaded):

System environment variables.env  ↓ (override).env.local  ↓ (override).env.{APP_ENV}  ↓ (override).env.{APP_ENV}.local

2. .gitignore Configuration

Recommended .gitignore configuration:

# Don't commit local configurations.env.local.env.*.local# Optional: also don't commit .env.env

Commit .env as a template, use .env.local to store local sensitive configurations.

3. Environment Variable Naming Convention

Recommended naming convention:

  • Use uppercase letters and underscores
  • Use prefixes to group related configurations (like DB_, CACHE_, REDIS_)
  • Use middle part for categories (like CACHE_BOOK_, CACHE_USER_)
  • Use suffixes for specific config items (like _HOST, _PORT, _DATABASE)

Example:

# Good namingDB_HOST=localhostDB_PORT=5432CACHE_REDIS_HOST=localhostCACHE_REDIS_PORT=6379# Not recommendeddbhost=localhost        # Lowercasedatabase-port=5432      # Uses hyphensredis.host=localhost    # Uses dots

4. Type Conversion Failures

When type conversion fails, the default value is returned:

// .env file// PORT=invalidport := env.Int("PORT", 8080)  // Returns 8080 (conversion failed, uses default)

If you need to know if conversion succeeded, use Lookup with manual conversion:

if value, exists := env.Lookup("PORT"); exists {    if port, err := strconv.Atoi(value); err == nil {        // Conversion succeeded    } else {        // Conversion failed        log.Printf("Invalid PORT value: %s", value)    }}

5. Sensitive Information Protection

Don’t commit sensitive information (passwords, keys, etc.) to version control:

// Use .env.local to store sensitive information// .env.local (not committed to git)DB_PASSWORD=super_secret_passwordAPI_SECRET_KEY=your_secret_key_here// .env (can be committed, provides template)DB_HOST=localhostDB_PORT=5432DB_NAME=mydbDB_USER=admin# DB_PASSWORD=  # Set in .env.local

6. Using Default Values

Reasonable use of default values can simplify configuration:

// Reasonable defaults for development environmentport := env.Int("PORT", 8080)debug := env.Bool("DEBUG", true)  // Default enabled in developmenttimeout := env.Duration("TIMEOUT", 30*time.Second)// This way .env file only needs to configure values that need to change

7. Structure Fill Limitations

Limitations of the Fill() function:

// ✓ Supportedtype Config struct {    Port int `env:"PORT"`  // Exported field + env tag}// ✗ Not supportedtype Config struct {    port int `env:"PORT"`  // Unexported field    Port int               // Missing env tag}

8. Signed Lookup Order

Understanding the Signed fallback mechanism:

// Environment variables:// CACHE_DRIVER=redis// CACHE_BOOK_DATABASE=10cache := env.Signed("CACHE", "BOOK")// Looking up DRIVER// 1. Try CACHE_BOOK_DRIVER (doesn't exist)// 2. Fallback to CACHE_DRIVER (found: "redis")// Looking up DATABASE// 1. Try CACHE_BOOK_DATABASE (found: 10)// 2. No need to fallback

Best Practices

1. Standard Initialization Flow

func main() {    // 1. Initialize environment variables    if err := env.Init(); err != nil {        log.Fatalf("Failed to initialize env: %v", err)    }    // 2. Lock to prevent runtime modifications    env.Lock()    // 3. Load configuration    cfg := loadConfig()    // 4. Start application    startApp(cfg)}func loadConfig() *Config {    cfg := &Config{}    if err := env.Fill(cfg); err != nil {        log.Fatalf("Failed to load config: %v", err)    }    return cfg}

2. Structured Configuration

// Organize related configurations into structurestype AppConfig struct {    Server   ServerConfig    Database DatabaseConfig    Redis    RedisConfig    Logger   LoggerConfig}type ServerConfig struct {    Port    int           `env:"SERVER_PORT"`    Host    string        `env:"SERVER_HOST"`    Timeout time.Duration `env:"SERVER_TIMEOUT"`}type DatabaseConfig struct {    Host     string `env:"DB_HOST"`    Port     int    `env:"DB_PORT"`    Database string `env:"DB_NAME"`    Username string `env:"DB_USER"`    Password string `env:"DB_PASSWORD"`}func LoadAppConfig() (*AppConfig, error) {    cfg := &AppConfig{}    if err := env.Fill(cfg); err != nil {        return nil, err    }    return cfg, nil}

3. Using Signed to Manage Multiple Instances

// Use different configurations for different service instancesfunc setupServices() {    authService := setupService("AUTH")    apiService := setupService("API")    notifyService := setupService("NOTIFICATION")}func setupService(name string) *Service {    cfg := env.Signed("SERVICE", name)    return &Service{        URL:     cfg.String("URL"),        Timeout: cfg.Duration("TIMEOUT", 30*time.Second),        APIKey:  cfg.String("API_KEY"),    }}

4. Provide Configuration Template

Provide a .env.example file as a configuration template in the project:

# .env.example - Configuration template (commit to git)# Application configurationAPP_ENV=developmentAPP_NAME=MyAppAPP_PORT=8080APP_DEBUG=true# Database configuration (fill in actual values)DB_HOST=localhostDB_PORT=5432DB_NAME=DB_USER=DB_PASSWORD=# Redis configurationREDIS_HOST=localhostREDIS_PORT=6379REDIS_DATABASE=0

Users copy and rename to .env, then fill in actual values.


Navigate Select esc Close search