A type-safe configuration loader for Gleam that supports JSON, YAML, and TOML with automatic format detection, environment variable resolution, and profile-based configuration. 🚀
gleam add yodel
import yodel
pub fn main() {
let assert Ok(config) = yodel.load("config.yaml")
let assert Ok(db_host) = yodel.get_string(config, "database.host")
let port = yodel.get_int_or(config, "database.port", 5432)
}
- Multiple Formats - Load JSON, YAML, or TOML with automatic format detection
- Profile-Based Configuration - Manage dev, staging, and production configs with separate files
- Environment Variables - Inject secrets and environment-specific values with
${VAR:default}
placeholders - Type-Safe - Compile-time safety with helpful error messages
- Dot Notation - Access nested values with
"database.host"
gleam add yodel
Yodel automatically detects the format from file extension or content:
import yodel
pub fn main() {
let assert Ok(config) = yodel.load("config.yaml")
// Type-safe value access
let assert Ok(host) = yodel.get_string(config, "database.host")
let assert Ok(port) = yodel.get_int(config, "database.port")
// Provide defaults for optional values
let cache_ttl = yodel.get_int_or(config, "cache.ttl", 3600)
}
Manage environment-specific configurations with profiles that automatically merge over your base configuration.
Directory structure:
config/
├── config.yaml # Base configuration (all environments)
├── config-dev.yaml # Development overrides
├── config-staging.yaml # Staging overrides
└── config-prod.yaml # Production overrides
config.yaml (base):
app:
name: myapp
version: 1.0.0
database:
host: localhost
port: 5432
pool_size: 10
config-prod.yaml (production overrides):
database:
host: prod.db.example.com
pool_size: 50
ssl: true
logging:
level: warn
Activate profiles via environment variable:
export YODEL_PROFILES=prod
import yodel
pub fn main() {
// Automatically loads config.yaml + config-prod.yaml
let assert Ok(config) = yodel.load("./config")
// Values from config-prod.yaml override config.yaml
let assert Ok(host) = yodel.get_string(config, "database.host")
// → "prod.db.example.com"
}
Note: Profile configs can use any supported format - mix and match YAML, TOML, and JSON as needed.
Or set profiles programmatically:
import yodel
pub fn main() {
let assert Ok(config) =
yodel.default_options()
|> yodel.with_profiles(["dev", "local"])
|> yodel.load_with_options("./config")
// Loads: config.yaml → config-dev.yaml → config-local.yaml
// Later profiles override earlier ones
}
The YODEL_PROFILES
environment variable takes precedence over programmatically set profiles,
allowing you to change environments at deployment time without code changes.
Inject environment-specific values and secrets using placeholders:
{
"database": {
"host": "${DATABASE_HOST:localhost}",
"password": "${DB_PASSWORD}"
},
"api": {
"key": "${API_KEY}",
"endpoint": "${API_ENDPOINT:https://api.example.com}"
}
}
export DATABASE_HOST=prod.db.example.com
export DB_PASSWORD=super-secret
export API_KEY=abc123
import yodel
pub fn main() {
let assert Ok(config) = yodel.load("config.json")
let assert Ok(host) = yodel.get_string(config, "database.host")
// → "prod.db.example.com" (from environment variable)
let assert Ok(password) = yodel.get_string(config, "database.password")
// → "super-secret" (from environment variable)
let assert Ok(endpoint) = yodel.get_string(config, "api.endpoint")
// → "https://api.example.com" (default value used)
}
Placeholder syntax:
${VAR_NAME}
- Simple substitution${VAR_NAME:default_value}
- With default value${VAR1:${VAR2:fallback}}
- Nested placeholders
import yodel
pub fn main() {
let assert Ok(config) =
yodel.default_options()
|> yodel.as_toml() // Force TOML format
|> yodel.with_resolve_strict() // Fail on unresolved placeholders
|> yodel.with_profiles(["prod"]) // Set active profiles
|> yodel.with_config_base_name("app") // Use app.toml instead of config.toml
|> yodel.with_profile_env_var("MY_PROFILES") // Read from MY_PROFILES env var
|> yodel.load_with_options("./config")
}
Yodel provides a simple, consistent API:
Loading: load()
and load_with_options()
for basic and advanced usage
Type-safe getters: get_string()
, get_int()
, get_float()
, get_bool()
Defaults: get_*_or()
variants return a default if key is missing
Parsing: parse_*()
functions convert between types
Configuration: Builder-style options with default_options()
, as_*()
, with_*()
functions
For the complete API reference, see https://hexdocs.pm/yodel.
config/
├── config.yaml # Shared configuration
├── config-dev.yaml # Local development
├── config-test.json # Test environment
├── config-staging.yaml # Staging environment
└── config-prod.toml # Production environment
Activate the appropriate profile for each environment:
# Development
export YODEL_PROFILES=dev
gleam run
# Staging
export YODEL_PROFILES=staging
gleam run
# Production
export YODEL_PROFILES=prod
gleam run
Profiles aren't just for environments - use them to layer any configuration changes:
config/
├── config.yaml # Base configuration
├── config-debug.yaml # Enable debug logging
├── config-metrics.yaml # Enable metrics collection
└── config-experimental.yaml # Enable experimental features
# Enable debug logging and metrics in production
export YODEL_PROFILES=prod,debug,metrics
gleam run
// Or activate features programmatically
let assert Ok(config) =
yodel.default_options()
|> yodel.with_profiles(["debug", "metrics"])
|> yodel.load_with_options("./config")
# config.toml
[database]
host = "${DB_HOST:localhost}"
port = "${DB_PORT:5432}"
name = "${DB_NAME:myapp}"
user = "${DB_USER:postgres}"
password = "${DB_PASSWORD}"
pool_size = "${DB_POOL_SIZE:10}"
import yodel
pub fn get_database_config() {
let assert Ok(config) = yodel.load("config.toml")
DatabaseConfig(
host: yodel.get_string_or(config, "database.host", "localhost"),
port: yodel.get_int_or(config, "database.port", 5432),
name: yodel.get_string_or(config, "database.name", "myapp"),
user: yodel.get_string_or(config, "database.user", "postgres"),
password: yodel.get_string(config, "database.password"),
pool_size: yodel.get_int_or(config, "database.pool_size", 10),
)
}
gleam test # Run the tests
This project is licensed under the Apache License 2.0.