Skip to content

빠른 시작

5분 안에 env 라이브러리를 시작하세요. 설치부터 실제 사용까지.

설치

bash
go get github.com/cybergodev/env

Go 1.25+

.env 파일 생성

프로젝트 루트 디렉터리에 .env 파일을 생성합니다:

bash
# 데이터베이스 설정
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=secret

# 애플리케이션 설정
DEBUG=true
APP_NAME=myapp
LOG_LEVEL=info

# 다중 값(쉼표 구분)
ALLOWED_HOSTS=localhost,example.com,api.example.com

최소 사용법

go
package main

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

func main() {
    // .env 파일을 로드하고 시스템 환경에 적용
    if err := env.Load(".env"); err != nil {
        panic(err)
    }

    // 환경 변수 가져오기
    host := env.GetString("DB_HOST", "localhost")
    port := env.GetInt("DB_PORT", 5432)

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

두 가지 사용 모드

env는 두 가지 사용 방식을 제공합니다:

모드사용법적용 시나리오
글로벌 모드env.Load() + env.GetString()단순 애플리케이션, 스크립트, 빠른 프로토타입
인스턴스 모드env.New() + loader.GetString()다중 인스턴스, 테스트 격리, 세밀한 수명 제어

글로벌 모드는 패키지 수준 함수를 사용하며, 내부적으로 기본 Loader 싱글톤을 유지합니다. env.Load() 호출 이후 모든 env.GetXxx()는 해당 인스턴스를 자동으로 사용합니다. 인스턴스 모드는 env.New()로 독립적인 Loader를 생성하며, 격리가 필요하거나 여러 구성을 동시에 관리해야 하는 시나리오에 적합합니다.

이 문서의 예제는 기본적으로 글로벌 모드를 사용합니다. 인스턴스 모드의 완전한 사용법은 다중 환경 구성 섹션을 참조하세요.

값 읽기 - 모든 타입

기본 타입

go
// === 기본값 포함 ===

// 문자열 - 찾지 못한 경우 기본값 "localhost" 반환
host := env.GetString("HOST", "localhost")

// 정수 (int64) - 찾지 못한 경우 기본값 8080 반환
port := env.GetInt("PORT", 8080)

// 불리언 - 찾지 못한 경우 기본값 false 반환
debug := env.GetBool("DEBUG", false)

// 시간 간격 - 찾지 못한 경우 기본값 30s 반환
timeout := env.GetDuration("TIMEOUT", 30*time.Second)


// === 기본값 미포함 ===

// 문자열 - 찾지 못한 경우 빈 문자열 "" 반환
host := env.GetString("HOST")

// 정수 (int64) - 찾지 못한 경우 0 반환
port := env.GetInt("PORT")

// 불리언 - 찾지 못한 경우 false 반환
debug := env.GetBool("DEBUG")

// 시간 간격 - 찾지 못한 경우 0 반환
timeout := env.GetDuration("TIMEOUT")

키 이름 해석

라이브러리는 다양한 키 이름 접근 방식을 지원합니다:

go
// JSON: {"app": {"name": "myapp"}}
// 저장 형태: APP_NAME=myapp

// 다음 방식 모두 해당 값에 접근 가능
name := env.GetString("APP_NAME")      // 플랫 키 이름(권장)
name := env.GetString("app.name")      // 점 표기 경로(자동 변환)
name := env.GetString("APP.NAME")      // 대문자 점 표기 경로

해석 규칙:

  1. 정확한 매칭: 정확한 키 이름 KEY를 먼저 찾습니다
  2. 대문자 변환: 소문자 키 이름은 대문자 버전을 시도합니다 keyKEY
  3. 경로 해석: 점 표기 경로를 밑줄로 변환합니다 app.nameAPP_NAME

불리언 값 지원

GetBool은 다음 값을 지원합니다(대소문자 구분 안 함):

참 값거짓 값
true, 1, yes, on, enabledfalse, 0, no, off, disabled

슬라이스 타입

go
// 문자열 슬라이스
hosts := env.GetSlice[string]("HOSTS", []string{"localhost"})

// 정수 슬라이스(int, int64, uint, uint64 지원)
ports := env.GetSlice[int64]("PORTS", []int64{80, 443})
portsInt := env.GetSlice[int]("PORTS")  // int 타입도 지원

// 부동소수점 슬라이스
rates := env.GetSlice[float64]("RATES", []float64{0.1, 0.2})

// 불리언 슬라이스
flags := env.GetSlice[bool]("FLAGS", []bool{true, false})

// Duration 슬라이스
timeouts := env.GetSlice[time.Duration]("TIMEOUTS")

해석 순서:

  1. 인덱스 키 KEY_0, KEY_1, KEY_2...를 먼저 찾습니다
  2. 인덱스 키가 없으면 KEY의 값을 쉼표로 구분하여 해석합니다
go
// 방식 1: 인덱스 키(권장)
// HOSTS_0=localhost
// HOSTS_1=example.com
hosts := env.GetSlice[string]("HOSTS")  // ["localhost", "example.com"]

// 방식 2: 쉼표 구분
// PORTS=80,443,8080
ports := env.GetSlice[int64]("PORTS")  // [80, 443, 8080]

확인 및 검색

go
// 키 존재 여부 확인
value, exists := env.Lookup("API_KEY")
if !exists {
    // 키가 존재하지 않음
}

// 모든 키 가져오기
keys := env.Keys()

// 모든 키-값 쌍 가져오기
all := env.All()

// 변수 개수 가져오기
count := env.Len()

보안 값

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

    // 원본 값 가져오기(암호화/복호화, API 호출 등 평문이 필요한 경우에만 호출)
    value := secret.Reveal()

    // 로그용 마스크 사용(유출 방지)
    log.Printf("API Key: %s", secret.Masked())  // 출력: [SECURE:32 bytes]
}

구조체 매핑

태그를 사용하여 환경 변수를 구조체에 매핑합니다:

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)
}
상세

구조체 매핑 가이드를 참조하세요.

구성 프리셋

라이브러리는 서로 다른 시나리오에 적합한 네 가지 프리셋 구성을 제공합니다:

프리셋용도특징
DefaultConfig()일반 시나리오안전한 기본값, 대부분의 상황에 적합
DevelopmentConfig()개발 환경느슨한 제한, 덮어쓰기 허용
TestingConfig()테스트 환경타이트한 제한, 덮어쓰기 허용, 단위 테스트에 적합
ProductionConfig()프로덕션 환경엄격한 검증 + 감사 로그
go
// 개발 환경 - 느슨한 제한
cfg := env.DevelopmentConfig()

// 테스트 환경 - 타이트한 제한
cfg := env.TestingConfig()

// 프로덕션 환경 - 엄격한 검증 + 감사 로그
cfg := env.ProductionConfig()

프리셋 상세 비교

기능DefaultDevelopmentTestingProduction
이미 존재하는 변수 덮어쓰기
파일이 없을 때 오류
감사 로그
YAML 구문
파일 크기 제한2MB10MB64KB64KB
최대 변수 수5005005050
금지 키 검사
값 검증

  • 개발 환경: DevelopmentConfig() 사용, 빠른 반복을 위해 느슨한 제한
  • 테스트 환경: TestingConfig() 사용, 테스트 격리를 위해 덮어쓰기 허용
  • 프로덕션 환경: ProductionConfig() 사용, 감사 및 엄격한 검증 활성화

다중 환경 구성

환경별 로드

go
// 환경에 따라 구성 파일 결정
goEnv := os.Getenv("GO_ENV")
if goEnv == "" {
    goEnv = "development"
}

// 한 번의 호출로 모든 구성 파일 로드(순서대로, 나중에 로드된 것이 먼저 로드된 것을 덮어씀)
env.Load(".env", ".env."+goEnv, ".env.local")

Loader 인스턴스 사용

더 많은 제어가 필요할 때는 Loader 인스턴스를 사용합니다:

go
package main

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

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

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

    // 파일 로드(순서대로, 나중에 로드된 것이 먼저 로드된 것을 덮어씀)
    if err := loader.LoadFiles(".env", ".env.production"); err != nil {
        panic(err)
    }

    // 필수 키 검증
    if err := loader.Validate(); err != nil {
        panic(err)
    }

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

다중 파일 및 다중 형식

다중 파일 로드

순서대로 로드하며, 나중에 로드된 것이 먼저 로드된 것을 덮어씁니다:

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

다중 형식 지원

파일 형식을 자동으로 감지합니다:

go
loader.LoadFiles("config.env", "settings.json", "secrets.yaml")
지원 형식
형식확장자감지 방식
.env.env파일 확장자
JSON.json파일 확장자
YAML.yaml, .yml파일 확장자

오류 처리

go
import "errors"

err := loader.LoadFiles(".env")
if err != nil {
    switch {
    case errors.Is(err, env.ErrFileNotFound):
        // 파일이 존재하지 않음
    case errors.Is(err, env.ErrFileTooLarge):
        // 파일이 너무 큼
    case errors.Is(err, env.ErrSecurityViolation):
        // 금지된 키(실제로는 *SecurityError 반환)
    default:
        // 기타 오류
    }

    // 키 형식이 잘못된 경우: 실제로는 *ValidationError 반환, Field=="key"
    var valErr *env.ValidationError
    if errors.As(err, &valErr) && valErr.Field == "key" {
        // 유효하지 않은 키 형식
    }
}
상세
go
// 파싱 오류 상세 정보
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
    fmt.Printf("파일 %s %d번 줄: %v\n", parseErr.File, parseErr.Line, parseErr.Err)
}

// 파일 오류 상세 정보
var fileErr *env.FileError
if errors.As(err, &fileErr) {
    fmt.Printf("파일 %s 작업 %s 실패: %v\n", fileErr.Path, fileErr.Op, fileErr.Err)
}

// 보안 오류 상세 정보
var secErr *env.SecurityError
if errors.As(err, &secErr) {
    fmt.Printf("보안 오류: %s - %s\n", secErr.Action, secErr.Reason)
}

다음 단계

심화 학습

API 레퍼런스

보안