docs
core
configuration
Configuration Management
Configuration Management
Mithril provides a powerful, environment-based configuration system that makes it easy to manage settings across different environments.
Environment Variables#
Mithril uses environment variables for configuration. Create a .env file in your project root:
# Application Configuration
APP_NAME="My Awesome App"
APP_ENV=development
APP_DEBUG=true
APP_HOST=localhost
APP_PORT=3000
APP_TIMEZONE=UTC
# Database Configuration
DB_DRIVER=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=my_awesome_app
DB_USER=postgres
DB_PASSWORD=password
DB_SSL_MODE=disable
# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key-32-characters-long
JWT_ACCESS_TOKEN_EXPIRY=15m
JWT_REFRESH_TOKEN_EXPIRY=7d
JWT_ALGORITHM=HS256
# Mail Configuration
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="My App"
# Storage Configuration
STORAGE_DRIVER=local
STORAGE_S3_BUCKET=my-bucket
STORAGE_S3_REGION=us-east-1
STORAGE_S3_ACCESS_KEY=your-access-key
STORAGE_S3_SECRET_KEY=your-secret-key
# Queue Configuration
QUEUE_DRIVER=redis
QUEUE_REDIS_HOST=localhost
QUEUE_REDIS_PORT=6379
QUEUE_REDIS_PASSWORD=
QUEUE_REDIS_DB=0
# Cache Configuration
CACHE_DRIVER=redis
CACHE_REDIS_HOST=localhost
CACHE_REDIS_PORT=6379
CACHE_REDIS_PASSWORD=
CACHE_REDIS_DB=1
# Session Configuration
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=
SESSION_SECURE=false
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
# CORS Configuration
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Requested-With
CORS_EXPOSED_HEADERS=
CORS_MAX_AGE=86400
CORS_ALLOW_CREDENTIALS=true
# Rate Limiting Configuration
RATE_LIMIT_ENABLED=true
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_WINDOW_MINUTES=15
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=false
RATE_LIMIT_SKIP_FAILED_REQUESTS=false
Configuration Files#
Application Configuration
// config/app.go
package config
import (
"time"
)
type AppConfig struct {
Name string `env:"APP_NAME" default:"Mithril App"`
Environment string `env:"APP_ENV" default:"development"`
Debug bool `env:"APP_DEBUG" default:"false"`
Host string `env:"APP_HOST" default:"localhost"`
Port int `env:"APP_PORT" default:"3000"`
Timezone string `env:"APP_TIMEZONE" default:"UTC"`
URL string `env:"APP_URL" default:"http://localhost:3000"`
Key string `env:"APP_KEY" required:"true"`
Locale string `env:"APP_LOCALE" default:"en"`
FallbackLocale string `env:"APP_FALLBACK_LOCALE" default:"en"`
}
func (c *AppConfig) IsDevelopment() bool {
return c.Environment == "development"
}
func (c *AppConfig) IsProduction() bool {
return c.Environment == "production"
}
func (c *AppConfig) IsStaging() bool {
return c.Environment == "staging"
}
func (c *AppConfig) IsTesting() bool {
return c.Environment == "testing"
}
func (c *AppConfig) GetAddress() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
Database Configuration
// config/database.go
package config
type DatabaseConfig struct {
Driver string `env:"DB_DRIVER" default:"postgres"`
Host string `env:"DB_HOST" default:"localhost"`
Port int `env:"DB_PORT" default:"5432"`
Name string `env:"DB_NAME" required:"true"`
User string `env:"DB_USER" required:"true"`
Password string `env:"DB_PASSWORD" required:"true"`
SSLMode string `env:"DB_SSL_MODE" default:"disable"`
Timezone string `env:"DB_TIMEZONE" default:"UTC"`
Charset string `env:"DB_CHARSET" default:"utf8mb4"`
MaxOpenConns int `env:"DB_MAX_OPEN_CONNS" default:"25"`
MaxIdleConns int `env:"DB_MAX_IDLE_CONNS" default:"25"`
ConnMaxLifetime time.Duration `env:"DB_CONN_MAX_LIFETIME" default:"5m"`
}
func (c *DatabaseConfig) GetDSN() string {
switch c.Driver {
case "postgres":
return c.getPostgreSQLDSN()
case "mysql":
return c.getMySQLDSN()
case "sqlite":
return c.Name
default:
return c.getPostgreSQLDSN()
}
}
func (c *DatabaseConfig) getPostgreSQLDSN() string {
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
c.Host, c.Port, c.User, c.Password, c.Name, c.SSLMode, c.Timezone)
}
func (c *DatabaseConfig) getMySQLDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=%s",
c.User, c.Password, c.Host, c.Port, c.Name, c.Charset, c.Timezone)
}
JWT Configuration
// config/jwt.go
package config
import "time"
type JWTConfig struct {
Secret string `env:"JWT_SECRET" required:"true"`
AccessTokenExpiry time.Duration `env:"JWT_ACCESS_TOKEN_EXPIRY" default:"15m"`
RefreshTokenExpiry time.Duration `env:"JWT_REFRESH_TOKEN_EXPIRY" default:"7d"`
Algorithm string `env:"JWT_ALGORITHM" default:"HS256"`
Issuer string `env:"JWT_ISSUER" default:"mithril"`
Audience string `env:"JWT_AUDIENCE" default:"mithril-users"`
}
Mail Configuration
// config/mail.go
package config
type MailConfig struct {
Driver string `env:"MAIL_DRIVER" default:"smtp"`
Host string `env:"MAIL_HOST" default:"localhost"`
Port int `env:"MAIL_PORT" default:"587"`
Username string `env:"MAIL_USERNAME"`
Password string `env:"MAIL_PASSWORD"`
Encryption string `env:"MAIL_ENCRYPTION" default:"tls"`
FromAddress string `env:"MAIL_FROM_ADDRESS" required:"true"`
FromName string `env:"MAIL_FROM_NAME" required:"true"`
Queue bool `env:"MAIL_QUEUE" default:"false"`
RetryAfter int `env:"MAIL_RETRY_AFTER" default:"5"`
}
Configuration Manager#
The configuration manager handles loading and validating configuration:
// pkg/config/manager.go
package config
import (
"os"
"reflect"
"strconv"
"time"
"github.com/joho/godotenv"
)
type Manager struct {
configs map[string]interface{}
}
func NewManagerFromEnv() *Manager {
// Load .env file
godotenv.Load()
manager := &Manager{
configs: make(map[string]interface{}),
}
return manager
}
func (m *Manager) RegisterConfig(name string, config interface{}) error {
// Load configuration from environment
if err := m.loadConfig(config); err != nil {
return err
}
m.configs[name] = config
return nil
}
func (m *Manager) GetConfig(name string) interface{} {
return m.configs[name]
}
func (m *Manager) loadConfig(config interface{}) error {
v := reflect.ValueOf(config).Elem()
t := reflect.TypeOf(config).Elem()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
// Get environment variable name
envKey := fieldType.Tag.Get("env")
if envKey == "" {
continue
}
// Get environment value
envValue := os.Getenv(envKey)
// Set field value
if err := m.setFieldValue(field, envValue, fieldType); err != nil {
return err
}
}
return nil
}
func (m *Manager) setFieldValue(field reflect.Value, value string, fieldType reflect.StructField) error {
if value == "" {
// Use default value if available
defaultValue := fieldType.Tag.Get("default")
if defaultValue != "" {
value = defaultValue
} else if fieldType.Tag.Get("required") == "true" {
return fmt.Errorf("required environment variable %s is not set", fieldType.Tag.Get("env"))
} else {
return nil
}
}
switch field.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if field.Type() == reflect.TypeOf(time.Duration(0)) {
duration, err := m.parseDuration(value)
if err != nil {
return err
}
field.Set(reflect.ValueOf(duration))
} else {
intValue, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
field.SetInt(intValue)
}
case reflect.Bool:
boolValue, err := strconv.ParseBool(value)
if err != nil {
return err
}
field.SetBool(boolValue)
}
return nil
}
func (m *Manager) parseDuration(value string) (time.Duration, error) {
// Support "d" for days and plain numbers as seconds
if value == "" {
return 0, nil
}
// Handle days
if strings.HasSuffix(value, "d") {
days, err := strconv.Atoi(strings.TrimSuffix(value, "d"))
if err != nil {
return 0, err
}
return time.Duration(days) * 24 * time.Hour, nil
}
// Handle plain numbers as seconds
if _, err := strconv.Atoi(value); err == nil {
seconds, _ := strconv.Atoi(value)
return time.Duration(seconds) * time.Second, nil
}
// Use standard time.ParseDuration
return time.ParseDuration(value)
}
Using Configuration#
In Your Application
// main.go
package main
import (
"log"
"github.com/mithril-framework/mithril/pkg/core"
"my-app/config"
)
func main() {
// Initialize configuration manager
configManager := config.NewManagerFromEnv()
// Register configurations
appConfig := &config.AppConfig{}
configManager.RegisterConfig("app", appConfig)
dbConfig := &config.DatabaseConfig{}
configManager.RegisterConfig("database", dbConfig)
jwtConfig := &config.JWTConfig{}
configManager.RegisterConfig("jwt", jwtConfig)
// Create application with configuration
app := core.NewApplication(configManager)
// Start server
log.Fatal(app.Listen(appConfig.GetAddress()))
}
In Controllers
// app/controllers/user_controller.go
package controllers
import (
"github.com/gofiber/fiber/v2"
"my-app/config"
)
type UserController struct {
config *config.AppConfig
}
func NewUserController(configManager *config.Manager) *UserController {
appConfig := configManager.GetConfig("app").(*config.AppConfig)
return &UserController{
config: appConfig,
}
}
func (c *UserController) Index(ctx *fiber.Ctx) error {
return ctx.JSON(fiber.Map{
"app_name": c.config.Name,
"environment": c.config.Environment,
"debug": c.config.Debug,
})
}
Environment-Specific Configuration#
Development Environment
# .env.development
APP_ENV=development
APP_DEBUG=true
DB_HOST=localhost
MAIL_DRIVER=log
CACHE_DRIVER=memory
Production Environment
# .env.production
APP_ENV=production
APP_DEBUG=false
DB_HOST=prod-db.example.com
MAIL_DRIVER=smtp
CACHE_DRIVER=redis
Testing Environment
# .env.testing
APP_ENV=testing
APP_DEBUG=true
DB_NAME=myapp_test
MAIL_DRIVER=array
CACHE_DRIVER=memory
Configuration Validation#
// pkg/config/validator.go
package config
import (
"fmt"
"reflect"
)
func ValidateConfig(config interface{}) error {
v := reflect.ValueOf(config).Elem()
t := reflect.TypeOf(config).Elem()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
// Check required fields
if fieldType.Tag.Get("required") == "true" {
if field.IsZero() {
return fmt.Errorf("required field %s is not set", fieldType.Name)
}
}
// Validate field values
if err := validateField(field, fieldType); err != nil {
return err
}
}
return nil
}
func validateField(field reflect.Value, fieldType reflect.StructField) error {
switch field.Kind() {
case reflect.String:
value := field.String()
// Check minimum length
if minLen := fieldType.Tag.Get("min"); minLen != "" {
if len(value) < parseMinLen(minLen) {
return fmt.Errorf("field %s must be at least %s characters", fieldType.Name, minLen)
}
}
// Check maximum length
if maxLen := fieldType.Tag.Get("max"); maxLen != "" {
if len(value) > parseMaxLen(maxLen) {
return fmt.Errorf("field %s must be at most %s characters", fieldType.Name, maxLen)
}
}
}
return nil
}
Configuration Caching#
# Cache configuration for better performance
go run . artisan config:cache
# Clear configuration cache
go run . artisan config:clear
Best Practices#
1. Use Environment Variables
- Never hardcode sensitive values
- Use
.envfiles for local development - Use environment variables in production
2. Provide Sensible Defaults
- Set default values for non-sensitive configuration
- Make required fields explicit
- Document all configuration options
3. Validate Configuration
- Validate configuration on startup
- Provide clear error messages
- Fail fast on invalid configuration
4. Environment-Specific Files
- Use different
.envfiles for different environments - Keep sensitive data out of version control
- Use configuration management tools in production
5. Security
- Never commit
.envfiles - Use strong secrets for JWT and encryption
- Rotate secrets regularly
- Use environment-specific secrets