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/envQuick 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) errorDescription:
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 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_ENVNotes:
- 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) errorDescription:
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 directoryif err := env.InitWithDir("/etc/myapp"); err != nil { log.Fatal(err)}Load - Load Specified File
func Load(filenames ...string) errorDescription:
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 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) boolDescription:
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-emptyString - Get String Value
func String(name string, fallback ...string) stringDescription:
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 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) intDescription:
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) float64Description:
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) boolDescription:
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.DurationDescription:
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) []byteDescription:
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) []stringDescription:
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]stringDescription:
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]stringDescription:
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]stringDescription:
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) errorDescription:
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) // trueSupported 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) SignerDescription:
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 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) 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 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) stringDescription:
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) boolDescription:
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) boolDescription:
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:8002Thread 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}.local2. .gitignore Configuration
Recommended .gitignore configuration:
# Don't commit local configurations.env.local.env.*.local# Optional: also don't commit .env.envCommit .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 dots4. 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.local6. 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 change7. 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 fallbackBest 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=0Users 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