Skip to content

Getting Started

Get started with the env library in 5 minutes, from installation to practical usage.

Installation

bash
go get github.com/cybergodev/env

Requirements

Go 1.25+

Create a .env File

Create a .env file in your project root directory:

bash
# Database configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=secret

# Application configuration
DEBUG=true
APP_NAME=myapp
LOG_LEVEL=info

# Multiple values (comma-separated)
ALLOWED_HOSTS=localhost,example.com,api.example.com

Minimal Usage

go
package main

import (
    "fmt"
    "github.com/cybergodev/env"
)

func main() {
    // Load .env file and apply to system environment
    if err := env.Load(".env"); err != nil {
        panic(err)
    }

    // Get environment variables
    host := env.GetString("DB_HOST", "localhost")
    port := env.GetInt("DB_PORT", 5432)

    fmt.Printf("Server: %s:%d\n", host, port)
}

Reading Values - All Types

Basic Types

go
// === With default values ===

// String - returns "localhost" if not found
host := env.GetString("HOST", "localhost")

// Integer (int64) - returns 8080 if not found
port := env.GetInt("PORT", 8080)

// Boolean - returns false if not found
debug := env.GetBool("DEBUG", false)

// Duration - returns 30s if not found
timeout := env.GetDuration("TIMEOUT", 30*time.Second)


// === Without default values ===

// String - returns empty string "" if not found
host := env.GetString("HOST")

// Integer (int64) - returns 0 if not found
port := env.GetInt("PORT")

// Boolean - returns false if not found
debug := env.GetBool("DEBUG")

// Duration - returns 0 if not found
timeout := env.GetDuration("TIMEOUT")

Key Resolution

The library supports multiple key access methods:

go
// JSON: {"app": {"name": "myapp"}}
// Stored as: APP_NAME=myapp

// All of the following can access the value
name := env.GetString("APP_NAME")      // Flat key name (recommended)
name := env.GetString("app.name")      // Dot path (auto-converted)
name := env.GetString("APP.NAME")      // Uppercase dot path

Resolution rules:

  1. Exact match: Exact key name KEY is looked up first
  2. Uppercase conversion: Lowercase key tries uppercase version key -> KEY
  3. Path resolution: Dot path converts to underscore app.name -> APP_NAME

Boolean Values

GetBool supports the following values (case-insensitive):

TruthyFalsy
true, 1, yes, on, enabledfalse, 0, no, off, disabled

Slice Types

go
// String slice
hosts := env.GetSlice[string]("HOSTS", []string{"localhost"})

// Integer slice (supports int, int64, uint, uint64)
ports := env.GetSlice[int64]("PORTS", []int64{80, 443})
portsInt := env.GetSlice[int]("PORTS")  // also supports int type

// Float slice
rates := env.GetSlice[float64]("RATES", []float64{0.1, 0.2})

// Boolean slice
flags := env.GetSlice[bool]("FLAGS", []bool{true, false})

// Duration slice
timeouts := env.GetSlice[time.Duration]("TIMEOUTS")

Resolution order:

  1. Indexed keys KEY_0, KEY_1, KEY_2... are looked up first
  2. If no indexed keys exist, the value of KEY is parsed by comma separation
go
// Method 1: Indexed keys (recommended)
// HOSTS_0=localhost
// HOSTS_1=example.com
hosts := env.GetSlice[string]("HOSTS")  // ["localhost", "example.com"]

// Method 2: Comma-separated
// PORTS=80,443,8080
ports := env.GetSlice[int64]("PORTS")  // [80, 443, 8080]

Checking and Lookup

go
// Check if a key exists
value, exists := env.Lookup("API_KEY")
if !exists {
    // Key does not exist
}

// Get all keys
keys := env.Keys()

// Get all key-value pairs
all := env.All()

// Get variable count
count := env.Len()

Secure Values

go
secret := env.GetSecure("API_KEY")
if secret != nil {
    defer secret.Release()

    // Get the raw value (call only when plaintext is needed, e.g., crypto, API calls)
    value := secret.Reveal()

    // Log with masking (prevent leakage)
    log.Printf("API Key: %s", secret.Masked())  // Output: [SECURE:32 bytes]
}

Struct Mapping

Map environment variables to a struct using tags:

go
package main

import (
    "fmt"
    "time"

    "github.com/cybergodev/env"
)

type Config struct {
    Host     string        `env:"DB_HOST" envDefault:"localhost"`
    Port     int64         `env:"DB_PORT" envDefault:"5432"`
    Password string        `env:"DB_PASSWORD"`
    Debug    bool          `env:"DEBUG" envDefault:"false"`
    Timeout  time.Duration `env:"TIMEOUT" envDefault:"30s"`
    Hosts    []string      `env:"ALLOWED_HOSTS"`
}

func main() {
    env.Load(".env")

    var cfg Config
    if err := env.ParseInto(&cfg); err != nil {
        panic(err)
    }

    fmt.Printf("%+v\n", cfg)
}
See Also

Struct Mapping guide.

Configuration Presets

The library provides four preset configurations for different scenarios:

PresetUse CaseFeatures
DefaultConfig()General useSafe defaults, suitable for most cases
DevelopmentConfig()DevelopmentRelaxed restrictions, allows override
TestingConfig()TestingTight restrictions, allows override, suitable for unit tests
ProductionConfig()ProductionStrict validation + audit logging
go
// Development - relaxed restrictions
cfg := env.DevelopmentConfig()

// Testing - tight restrictions
cfg := env.TestingConfig()

// Production - strict validation + audit logging
cfg := env.ProductionConfig()

Detailed Preset Comparison

FeatureDefaultDevelopmentTestingProduction
Override existing variablesNoYesYesNo
Error on missing fileNoNoNoYes
Audit loggingNoNoNoYes
YAML syntaxNoYesNoNo
File size limit2MB10MB64KB64KB
Max variables5005005050
Forbidden key checkYesYesYesYes
Value validationYesYesYesYes

Selection Guide

  • Development: Use DevelopmentConfig() with relaxed restrictions for rapid iteration
  • Testing: Use TestingConfig() with override support for test isolation
  • Production: Use ProductionConfig() with audit logging and strict validation

Multi-Environment Configuration

Loading by Environment

go
// Determine config file based on environment
goEnv := os.Getenv("GO_ENV")
if goEnv == "" {
    goEnv = "development"
}

// Load all config files in a single call (in order, later files override earlier ones)
env.Load(".env", ".env."+goEnv, ".env.local")

Using a Loader Instance

For more control, use a Loader instance:

go
package main

import (
    "fmt"
    "github.com/cybergodev/env"
)

func main() {
    // Create configuration
    cfg := env.ProductionConfig()
    cfg.RequiredKeys = []string{"DB_HOST", "API_KEY"}

    // Create loader
    loader, err := env.New(cfg)
    if err != nil {
        panic(err)
    }
    defer loader.Close()

    // Load files (in order, later files override earlier ones)
    if err := loader.LoadFiles(".env", ".env.production"); err != nil {
        panic(err)
    }

    // Validate required keys
    if err := loader.Validate(); err != nil {
        panic(err)
    }

    // Usage
    host := loader.GetString("DB_HOST")
    fmt.Println("Host:", host)
}

Multiple Files and Formats

Multiple File Loading

Files are loaded in order; later files override earlier ones:

go
env.Load(".env", "config.json", "config.yaml")
go
loader.LoadFiles(".env", ".env.local")

Multi-format Support

File format is automatically detected:

go
loader.LoadFiles("config.env", "settings.json", "secrets.yaml")
Supported Formats
FormatExtensionDetection Method
.env.envFile extension
JSON.jsonFile extension
YAML.yaml, .ymlFile extension

Error Handling

go
import "errors"

err := loader.LoadFiles(".env")
if err != nil {
    switch {
    case errors.Is(err, env.ErrFileNotFound):
        // File not found
    case errors.Is(err, env.ErrFileTooLarge):
        // File too large
    case errors.Is(err, env.ErrSecurityViolation):
        // Forbidden key (actually returns *SecurityError)
    default:
        // Other errors
    }

    // Invalid key format: actually returns *ValidationError, Field=="key"
    var valErr *env.ValidationError
    if errors.As(err, &valErr) && valErr.Field == "key" {
        // Invalid key format
    }
}
Getting Detailed Error Information
go
// Parse error details
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
    fmt.Printf("File %s line %d: %v\n", parseErr.File, parseErr.Line, parseErr.Err)
}

// File error details
var fileErr *env.FileError
if errors.As(err, &fileErr) {
    fmt.Printf("File %s operation %s failed: %v\n", fileErr.Path, fileErr.Op, fileErr.Err)
}

// Security error details
var secErr *env.SecurityError
if errors.As(err, &secErr) {
    fmt.Printf("Security error: %s - %s\n", secErr.Action, secErr.Reason)
}

Next Steps

Deep Dive

API Reference

Security