Learn Gin - Configuration & Environment
Series/Learn Gin/Episode 10
Episode 10 of 23

Learn Gin - Configuration & Environment

This episode manages Gin application configuration: environment variables with os.Getenv, .env files via godotenv, and Viper for centralized configuration. You'll also learn to structure a config struct and implement graceful shutdown with http.Server and signal.NotifyContext.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

An application shouldn't load secrets or settings from code. This episode 10 dissects configuration & environment: how to read environment variables, load .env files for development, manage complex configuration with Viper, and — just as importantly — stop the server cleanly with graceful shutdown.

Why is this part crucial? Database connections, ports, and tokens should be able to differ between development, staging, and production without changing code. Graceful shutdown ensures in-flight requests finish before the process dies, so users don't hit errors mid-processing.

Environment Variables with os.Getenv

Reading and Verifying

The most basic way to read configuration is os.Getenv. For variables that must exist, combine it with os.LookupEnv:

Read environment variables
port := os.Getenv("PORT")
if port == "" {
    port = "8080"
}
 
dbURL, ok := os.LookupEnv("DATABASE_URL")
if !ok {
    log.Fatal("DATABASE_URL wajib diisi")
}

os.Getenv("PORT") returns an empty string if the variable doesn't exist, so you need a fallback. os.LookupEnv returns two values so you know for sure whether a variable is defined — important for required variables like DATABASE_URL.

godotenv for .env Files

Loading a .env File During Development

In development, setting dozens of variables through the terminal isn't practical. godotenv loads a .env file into the environment:

Install godotenv
go get github.com/joho/godotenv
The .env file
PORT=8080
DATABASE_URL=postgres://arman:rahasia@localhost:5432/belajargin?sslmode=disable
REDIS_ADDR=localhost:6379
LOG_LEVEL=info
Load .env at the start of main
func main() {
    if err := godotenv.Load(); err != nil {
        log.Println("tidak ada .env, pakai environment system")
    }
    cfg := config.Load()
    // ...
}

Call godotenv.Load() at the beginning of main before reading configuration. Never commit a .env file — use .env.example as a template. In production, variables are injected directly by the orchestrator or platform, not from a file.

Centralized Configuration with Viper

Reading Files and Environment at Once

For more complex configuration, viper combines config files, environment variables, and flags:

Install Viper
go get github.com/spf13/viper
Initialize Viper
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AutomaticEnv()
 
if err := viper.ReadInConfig(); err != nil {
    log.Printf("config file tidak terbaca: %v", err)
}

viper.AutomaticEnv() lets every key be overridden by an environment variable following the same naming pattern. For example, the key database.url can be overridden by the variable DATABASE.URL or another pattern per your configuration. This establishes a priority: environment beats file.

Config Struct and Injection into Handlers

The Config Struct

Collect all settings into one struct so it can be injected:

config.go
type Config struct {
    Port        string
    DatabaseURL string
    MaxConns    int
    RedisAddr   string
    LogLevel    string
}
 
func Load() Config {
    return Config{
        Port:        os.Getenv("PORT"),
        DatabaseURL: os.Getenv("DATABASE_URL"),
        MaxConns:    mustInt("MAX_CONNS", 10),
        RedisAddr:   os.Getenv("REDIS_ADDR"),
        LogLevel:    os.Getenv("LOG_LEVEL"),
    }
}

The function config.Load() Config becomes the single place that reads the environment. Handlers and services no longer need to call os.Getenv — they receive Config through constructors, following the injection pattern from episode 8.

Injecting into Handlers

With injection, handlers can be tested with any config:

NewServer(cfg config.Config) injects the config into every layer. Because all dependencies come from one place, switching environments only means changing environment values, not code.

Graceful Shutdown

Using http.Server and signal.NotifyContext

engine.Run blocks the goroutine directly, making a clean stop difficult. Replace it with http.Server and handle signals:

Graceful shutdown
srv := &http.Server{
    Addr:         ":" + cfg.Port,
    Handler:      r,
    ReadTimeout:  10 * time.Second,
    WriteTimeout: 15 * time.Second,
}
 
go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatalf("server gagal: %v", err)
    }
}()
 
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
 
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
 
if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Fatalf("shutdown gagal: %v", err)
}
log.Println("server berhenti dengan bersih")

signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) cancels the context when the user presses Ctrl+C or an orchestrator sends SIGTERM. Then srv.Shutdown(shutdownCtx) waits for active requests to finish for up to 10 seconds before closing connections. This is the pattern we'll reuse in episode 21 for the production architecture.

Closing

Key takeaways:

  • os.Getenv with a fallback, os.LookupEnv for required variables.
  • godotenv loads .env files for development; never commit them.
  • viper combines config files and environment with environment taking priority.
  • Collect settings in config.Config and inject it into handlers.
  • http.Server + signal.NotifyContext for graceful shutdown.
  • srv.Shutdown gives active requests time to finish before the process exits.

In the next episode, episode 11, we'll dissect error handling & logging — capturing errors with c.Error, custom error types, an error handler middleware with unified JSON responses, and integrating slog into Gin middleware for structured logs.

Learn Gin - Configuration & Environment | Learn Gin