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 main
import (
"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 configuration
APP_ENV=development
APP_NAME=MyApp
PORT=8080
DEBUG=true
# Database configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
DB_USER=admin
DB_PASSWORD=secret
# Cache configuration
CACHE_DRIVER=redis
CACHE_DATABASE=1
CACHE_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.envfile; if not specified, uses the current program's runtime directory
Loading Order:
- System environment variables
.envfile.env.localfile (higher priority, can override.env).env.{APP_ENV}file (based onAPP_ENVvariable).env.{APP_ENV}.localfile (highest priority)
Examples:
// Use current directory
if err := env.Init(); err != nil {
log.Fatal(err)
}
// Specify configuration directory
if 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.envfile
Example:
// Load from specified directory
if 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 initialization
env.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 returnsErrLockederror - Calling
Updates()orClean()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:
trueif variable exists and value is non-empty,falseif 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 Lookup
value, 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 namefallback: Optional default value (variadic parameter, only uses the first one)
Example:
// No default value, returns empty string if doesn't exist
host := env.String("DB_HOST")
// With default value
host := 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 failure
port := env.Int("PORT")
// With default value
port := 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=5m
timeout := 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, api
origins := 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 value
hosts := 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=admin
dbConfig := 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 variables
nonEmpty := 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=5432
type 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 Config
if err := env.Fill(&cfg); err != nil {
log.Fatal(err)
}
fmt.Println(cfg.AppName) // MyApp
fmt.Println(cfg.Port) // 8080
fmt.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:
- First lookup:
PREFIX_CATEGORY_KEY - 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 configuration
bookCache := 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 configuration
userCache := 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") // 1
defaultCache.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) boolString(key string, fallback ...string) stringBytes(key string, fallback ...[]byte) []byteInt(key string, fallback ...int) intDuration(key string, fallback ...time.Duration) time.DurationBool(key string, fallback ...bool) boolList(key string, fallback ...[]string) []stringMap(prefix string) map[string]stringWhere(filter func(name, value string) bool) map[string]stringFill(structure any) error
Use Cases:
Suitable for configuring similar but different environment variables for different modules, environments, or tenants:
// Multi-tenant configuration
tenant1DB := env.Signed("DB", "TENANT1")
tenant2DB := env.Signed("DB", "TENANT2")
// Multi-service configuration
authService := env.Signed("SERVICE", "AUTH")
apiService := env.Signed("SERVICE", "API")
// Multi-environment cache
prodCache := 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 /app
env.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=development
if 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 variables
success := 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.go
func 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 purposes
func 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=true
type 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:8004
func 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
}
// Usage
serviceURLs := loadServiceURLs()
authURL := serviceURLs["auth"] // http://auth:8001
apiURL := 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
| Function | Description | Thread-Safe |
|---|---|---|
Init(root ...string) error | Initialize env vars | No |
InitWithDir(dir string) error | Initialize from dir | No |
Load(filenames ...string) error | Load specified files | No |
Lock() | Lock env vars | Yes |
Signed(prefix, category string) Signer | Create grouped querier | Yes |
Path(path ...string) string | Get relative path | Yes |
Is(env ...string) bool | Check app environment | Yes |
Updates(data map[string]string) bool | Update env vars | No |
Query Functions (Thread-Safe)
| Function | Description |
|---|---|
Lookup(name string) (string, bool) | Query env var |
Exists(name string) bool | Check existence |
String(name string, fallback ...string) string | Get string value |
Bytes(name string, fallback ...[]byte) []byte | Get byte slice |
Int(name string, fallback ...int) int | Get integer value |
Float(name string, fallback ...float64) float64 | Get float value |
Bool(name string, fallback ...bool) bool | Get boolean value |
Duration(name string, fallback ...time.Duration) time.Duration | Get time duration |
List(name string, fallback ...[]string) []string | Get string list |
Map(prefix string) map[string]string | Get vars with same prefix |
Where(filter func(name, value string) bool) map[string]string | Custom filter query |
Fill(structure any) error | Fill structure |
All() map[string]string | Get 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 naming
DB_HOST=localhost
DB_PORT=5432
CACHE_REDIS_HOST=localhost
CACHE_REDIS_PORT=6379
# Not recommended
dbhost=localhost # Lowercase
database-port=5432 # Uses hyphens
redis.host=localhost # Uses dots
4. Type Conversion Failures
When type conversion fails, the default value is returned:
// .env file
// PORT=invalid
port := 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_password
API_SECRET_KEY=your_secret_key_here
// .env (can be committed, provides template)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
DB_USER=admin
# DB_PASSWORD= # Set in .env.local
6. Using Default Values
Reasonable use of default values can simplify configuration:
// Reasonable defaults for development environment
port := env.Int("PORT", 8080)
debug := env.Bool("DEBUG", true) // Default enabled in development
timeout := 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:
// ✓ Supported
type Config struct {
Port int `env:"PORT"` // Exported field + env tag
}
// ✗ Not supported
type 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=10
cache := 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 structures
type 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 instances
func 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 configuration
APP_ENV=development
APP_NAME=MyApp
APP_PORT=8080
APP_DEBUG=true
# Database configuration (fill in actual values)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
# Redis configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DATABASE=0
Users copy and rename to .env, then fill in actual values.
Related Links
- GitHub Repository
- godotenv - .env file parsing library
- go-slim.dev/cast - Type conversion library