Skip to content

Configuration Guide

Complete guide to configuring the Go REST API Boilerplate (GRAB) with the Viper-based configuration system.


📋 Overview

The application uses a layered configuration system powered by Viper that supports multiple configuration sources with clear precedence rules.

Configuration Precedence (Highest to Lowest)

1. Environment Variables (.env file) 
   ↓ (overrides)
2. Environment-specific Config Files (config.{environment}.yaml)
   ↓ (overrides)  
3. Base Config File (config.yaml)
   ↓ (overrides)
4. Default Values (built into application)

This means environment variables always win, followed by environment-specific config files, then the base config file, and finally built-in defaults.


🗂️ Configuration Files

Base Configuration (configs/config.yaml)

The main configuration file with all available options and documentation:

# ===========================================
# Go REST API Boilerplate - Base Configuration
# ===========================================

app:
  name: "GRAB API"                  # Application name
  environment: "development"        # Environment: development|staging|production
  debug: true                       # Enable debug mode

database:
  host: "db"                        # Database host
  port: 5432                        # Database port  
  user: "postgres"                  # Database user
  password: ""                      # Database password (set via env var)
  name: "grab"                      # Database name
  sslmode: "disable"                # SSL mode: disable|require|verify-ca|verify-full

jwt:
  secret: ""                        # JWT secret (required, set via env var)
  ttlhours: 24                      # JWT token TTL in hours

server:
  port: "8080"                      # Server port
  readtimeout: 10                   # Read timeout in seconds
  writetimeout: 10                  # Write timeout in seconds
  idletimeout: 120                  # Idle timeout in seconds
  shutdowntimeout: 30               # Graceful shutdown timeout in seconds
  maxheaderbytes: 1048576           # Max header bytes (1MB)

logging:
  level: "info"                     # Log level: debug|info|warn|error

ratelimit:
  enabled: false                    # Enable rate limiting
  requests: 100                     # Max requests per window
  window: "1m"                      # Time window (e.g., "1m", "1h", "30s")

Environment-Specific Configuration

The application automatically loads environment-specific config files based on the APP_ENVIRONMENT variable:

Development (configs/config.development.yaml)

app:
  name: "GRAB API (development)"
  environment: "development"
  debug: true

database:
  host: "db"
  password: "postgres"              # Default for development
  sslmode: "disable"                # SSL disabled for convenience

jwt:
  # Generate: make generate-jwt-secret
  ttlhours: 24

Production (configs/config.production.yaml)

app:
  name: "GRAB API"
  environment: "production"
  debug: false                      # Disable debug in production

database:
  password: ""                      # Must be set via DATABASE_PASSWORD
  sslmode: "require"                # SSL required in production

jwt:
  # Generate: make generate-jwt-secret
  ttlhours: 24

Staging (configs/config.staging.yaml)

app:
  name: "GRAB API (staging)"
  environment: "staging"
  debug: false

database:
  sslmode: "require"                # SSL required in staging

jwt:
  # Generate: make generate-jwt-secret
  ttlhours: 24

🌍 Environment Variables

Complete Reference

All configuration values can be overridden with environment variables using the format: SECTION_KEY=value.

Application Configuration

Variable Default Description Accepted Values
APP_NAME "GRAB API" Application name Any string
APP_ENVIRONMENT "development" Runtime environment development, staging, production
APP_DEBUG true Enable debug mode true, false

Database Configuration

Variable Default Description Accepted Values
DATABASE_HOST "db" Database host Hostname or IP
DATABASE_PORT 5432 Database port 1-65535
DATABASE_USER "postgres" Database user Any string
DATABASE_PASSWORD "" Database password Any string (required in production)
DATABASE_NAME "grab" Database name Valid PostgreSQL database name
DATABASE_SSLMODE "disable" SSL mode disable, require, verify-ca, verify-full

JWT Configuration

CRITICAL SECURITY REQUIREMENT: JWT_SECRET must be set and secure.

Variable Default Description Accepted Values
JWT_SECRET "" (REQUIRED) JWT signing secret Min 32 chars (64+ for production). Must be cryptographically random.
JWT_ACCESS_TOKEN_TTL "15m" Access token lifetime Duration string (e.g., "15m", "1h")
JWT_REFRESH_TOKEN_TTL "168h" Refresh token lifetime (7 days) Duration string (e.g., "168h", "7d")
JWT_TTLHOURS 24 Token TTL in hours (deprecated) Positive integer

Security Notes:

  • Generate secure secret: make generate-jwt-secret
  • Auto-generated: make quick-start creates one automatically if missing
  • Rejected patterns: "secret", "password", "test", "dev", "123456", etc.
  • ⚠️ All environments require: 32+ characters minimum

Server Configuration

Variable Default Description Accepted Values
SERVER_PORT "8080" Server port "1" to "65535"
SERVER_READTIMEOUT 10 Read timeout (seconds) Non-negative integer
SERVER_WRITETIMEOUT 10 Write timeout (seconds) Non-negative integer
SERVER_IDLETIMEOUT 120 Idle timeout (seconds) Non-negative integer
SERVER_SHUTDOWNTIMEOUT 30 Graceful shutdown timeout (seconds) Non-negative integer
SERVER_MAXHEADERBYTES 1048576 Max header bytes (1MB) Non-negative integer

Logging Configuration

Variable Default Description Accepted Values
LOGGING_LEVEL "info" Log level debug, info, warn, error

Rate Limiting Configuration

Variable Default Description Accepted Values
RATELIMIT_ENABLED false Enable rate limiting true, false
RATELIMIT_REQUESTS 100 Max requests per window Positive integer
RATELIMIT_WINDOW "1m" Time window Go duration format (1s, 1m, 1h)

🔧 Configuration Examples

Example 1: Development Setup

Create .env file:

# Development environment
APP_ENVIRONMENT=development
APP_DEBUG=true

# Database (Docker setup)
DATABASE_HOST=db
DATABASE_PASSWORD=postgres

# JWT (development only - use stronger secret in production)
JWT_SECRET=dev-secret-change-in-production

# Logging
LOGGING_LEVEL=debug

Example 2: Production Setup

Create .env file:

# Production environment
APP_ENVIRONMENT=production
APP_DEBUG=false

# Database (production values)
DATABASE_HOST=prod-db.example.com
DATABASE_PASSWORD=super-secure-password
DATABASE_SSLMODE=require

# JWT (strong production secret)
JWT_SECRET=this-is-a-very-strong-production-jwt-secret-that-is-at-least-32-characters-long
JWT_TTLHOURS=1

# Server
SERVER_PORT=8080
SERVER_READTIMEOUT=30
SERVER_WRITETIMEOUT=30
SERVER_IDLETIMEOUT=120
SERVER_SHUTDOWNTIMEOUT=30
SERVER_MAXHEADERBYTES=1048576

# Logging
LOGGING_LEVEL=warn

# Rate Limiting
RATELIMIT_ENABLED=true
RATELIMIT_REQUESTS=60
RATELIMIT_WINDOW=1m

Example 3: Docker Compose Override

# docker-compose.override.yml
version: '3.8'

services:
  app:
    environment:
      - APP_ENVIRONMENT=staging
      - DATABASE_HOST=staging-db
      - JWT_SECRET=staging-jwt-secret-32-chars-min
      - LOGGING_LEVEL=info
      - RATELIMIT_ENABLED=true

⚡ Quick Setup Commands

Copy and Configure

# Copy example file
cp .env.example .env

# Edit with your values
nano .env

Verify Configuration

# Check if app starts correctly
make up

# Check health endpoint
curl http://localhost:8080/health

# View configuration (debug info)
docker logs go-rest-api-boilerplate_app_1 | grep -i config

🔒 Security Best Practices

Production Configuration

  1. Always use environment variables for secrets:

    # ✅ Good - via environment
    JWT_SECRET=strong-production-secret
    DATABASE_PASSWORD=secure-db-password
    
    # ❌ Bad - hardcoded in config file
    jwt.secret: "hardcoded-secret"
    

  2. Use strong JWT secrets (32+ characters):

    # ✅ Good - 32+ characters
    JWT_SECRET=this-is-a-very-strong-jwt-secret-for-production-use-only
    
    # ❌ Bad - too short
    JWT_SECRET=secret123
    

  3. Enable SSL for database connections:

    # ✅ Good - SSL required
    DATABASE_SSLMODE=require
    
    # ❌ Bad - SSL disabled
    DATABASE_SSLMODE=disable
    

  4. Use appropriate log levels:

    # ✅ Good - production logging
    LOGGING_LEVEL=warn
    
    # ❌ Bad - too verbose in production
    LOGGING_LEVEL=debug
    

Environment Isolation

  • Development: Use config.development.yaml + .env
  • Staging: Use config.staging.yaml + environment variables
  • Production: Use config.production.yaml + secure environment variables

🐛 Troubleshooting

Common Configuration Issues

1. Configuration Not Loading

Problem: Changes to config files not being applied.

Solution:

# Check which config file is being loaded
APP_ENVIRONMENT=development go run cmd/server/main.go

# Verify file exists
ls -la configs/config.development.yaml

# Check environment variable
echo $APP_ENVIRONMENT

2. Environment Variables Not Working

Problem: Environment variables not overriding config files.

Solution:

# Check variable format (must use underscores)
DATABASE_HOST=localhost  # ✅ Correct
database.host=localhost  # ❌ Wrong

# Check if .env file is loaded
cat .env | grep DATABASE_HOST

# Restart application after changes
make restart

3. Validation Errors

Problem: Application fails to start with validation errors.

Solutions:

JWT Secret Missing:

# Error: JWT secret is required
JWT_SECRET=your-secret-here

# Production: minimum 32 characters required
JWT_SECRET=this-is-a-32-character-secret-key

Database Password Missing in Production:

# Error: database.password is required in production
DATABASE_PASSWORD=secure-password

Invalid SSL Mode in Production:

# Error: SSL mode cannot be 'disable' in production
DATABASE_SSLMODE=require

4. Port Already in Use

Problem: Server port is already occupied.

Solution:

# Check what's using the port
lsof -i :8080

# Use different port
SERVER_PORT=8081

# Or kill the process
lsof -ti:8080 | xargs kill -9


📚 Advanced Configuration

Custom Configuration Loading

For advanced use cases, you can load configuration programmatically:

package main

import (
    "github.com/vahiiiid/go-rest-api-boilerplate/internal/config"
)

func main() {
    // Load from specific file
    cfg, err := config.LoadConfig("/path/to/custom-config.yaml")
    if err != nil {
        log.Fatal(err)
    }

    // Load with environment detection
    cfg, err = config.LoadConfig("")  // Uses APP_ENVIRONMENT
    if err != nil {
        log.Fatal(err)
    }
}

Testing Configuration

// Use test helper for consistent test config
func TestMyFunction(t *testing.T) {
    cfg := config.NewTestConfig()
    // cfg is now pre-configured for testing
}