Configuration
Nimbus uses environment-driven configuration inspired by Laravel conventions. Each subsystem has its own config file that reads from .env via godotenv.
How it works
When the application boots, bin/server.go calls config.Load() as the very first step. This:
- Reads the
.envfile usinggodotenv.Load(). - Calls each config loader (
loadApp(),loadDatabase(), etc.). - Populates typed Go structs that you access via
config.App,config.Database, etc.
// config/config.go
package config
import "github.com/joho/godotenv"
func Load() {
_ = godotenv.Load()
loadApp()
loadDatabase()
}
Environment variables
Create a .env file in your project root:
PORT=3333
APP_ENV=development
APP_NAME=myapp
DB_DRIVER=sqlite
DB_DSN=database.sqlite
The .env.example file (committed to version control) documents all available variables. Copy it to .env and fill in your values. Never commit .env itself.
Environment helpers
config/env.go provides helper functions to safely read environment variables with fallback values:
// config/env.go
func env(key, fallback string) string // string with default
func envInt(key string, fallback int) int // int with default
func envBool(key string, fallback bool) bool // bool with default
Config files
Each subsystem has its own file inside config/. Here is the pattern:
config/app.go
package config
type AppConfig struct {
Name string
Env string
Port int
}
var App AppConfig
func loadApp() {
App = AppConfig{
Name: env("APP_NAME", "nimbus"),
Env: env("APP_ENV", "development"),
Port: envInt("PORT", 3333),
}
}
config/database.go
package config
type DatabaseConfig struct {
Driver string
DSN string
}
var Database DatabaseConfig
func loadDatabase() {
Database = DatabaseConfig{
Driver: env("DB_DRIVER", "sqlite"),
DSN: env("DB_DSN", "database.sqlite"),
}
}
config/auth.go
package config
type AuthConfig struct {
DefaultGuard string
Stateless StatelessTokenConfig
}
type StatelessTokenConfig struct {
Driver string
Secret string
ExpiresIn time.Duration
}
var Auth AuthConfig
func loadAuth() {
Auth = AuthConfig{
DefaultGuard: env("AUTH_GUARD", "session"),
Stateless: StatelessTokenConfig{
Driver: env("AUTH_TOKEN_DRIVER", "jwt"),
Secret: env("AUTH_TOKEN_SECRET", ""),
ExpiresIn: envDuration("AUTH_TOKEN_EXPIRES_IN", 24 * time.Hour),
},
}
}
Accessing config
Import the config package and access the typed struct fields:
import "myapp/config"
fmt.Println(config.App.Name) // "myapp"
fmt.Println(config.App.Port) // 3333
fmt.Println(config.Database.Driver) // "sqlite"
Adding a new config
To add configuration for a new subsystem (e.g. mail):
- Create
config/mail.gowith a struct, a package-level var, and aloadMail()function. - Add
loadMail()to theLoad()function inconfig/config.go. - Add the environment variables to
.env.example.
// config/mail.go
package config
type MailConfig struct {
Driver string
SMTPHost string
SMTPPort int
From string
}
var Mail MailConfig
func loadMail() {
Mail = MailConfig{
Driver: env("MAIL_DRIVER", "smtp"),
SMTPHost: env("SMTP_HOST", "localhost"),
SMTPPort: envInt("SMTP_PORT", 587),
From: env("MAIL_FROM", "noreply@example.com"),
}
}
Environment validation
Validate that required environment variables are set at boot time. The app panics with a clear message if any required variable is missing.
import "github.com/CodeSyncr/nimbus/config"
func init() {
config.ValidateEnv(
config.Required("APP_KEY"),
config.Required("DB_DSN"),
config.Required("REDIS_URL"),
)
}
// If DB_DSN is not set:
// panic: "environment validation failed: DB_DSN is required"
Use EnvRule for custom validation beyond presence checks:
config.ValidateEnv(
config.Required("PORT"),
config.EnvRule{
Key: "APP_ENV",
Message: "APP_ENV must be development, staging, or production",
Validate: func(val string) bool {
return val == "development" || val == "staging" || val == "production"
},
},
)