# Yodel
### 🎶 Yo-de-lay-ee-configs! <!-- markdownlint-disable-line MD001 MD026 -->
A type-safe configuration loader for Gleam that supports JSON, YAML, and TOML with automatic format detection,
environment variable resolution, and profile-based configuration. 🚀
[](https://hex.pm/packages/yodel)
[](https://hexdocs.pm/yodel/)
```sh
gleam add yodel
```
```gleam
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)
}
```
## Features
- **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"`
## Installation
```sh
gleam add yodel
```
## Quick Start
Yodel automatically detects the format from file extension or content:
```gleam
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)
}
```
## Profile-Based Configuration
Manage environment-specific configurations with profiles that automatically merge over your base configuration.
**Directory structure:**
```text
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):
```yaml
app:
name: myapp
version: 1.0.0
database:
host: localhost
port: 5432
pool_size: 10
```
**config-prod.yaml** (production overrides):
```yaml
database:
host: prod.db.example.com
pool_size: 50
ssl: true
logging:
level: warn
```
**Activate profiles via environment variable:**
```sh
export YODEL_PROFILES=prod
```
```gleam
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:**
```gleam
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.
## Environment Variable Resolution
Inject environment-specific values and secrets using placeholders:
```json
{
"database": {
"host": "${DATABASE_HOST:localhost}",
"password": "${DB_PASSWORD}"
},
"api": {
"key": "${API_KEY}",
"endpoint": "${API_ENDPOINT:https://api.example.com}"
}
}
```
```sh
export DATABASE_HOST=prod.db.example.com
export DB_PASSWORD=super-secret
export API_KEY=abc123
```
```gleam
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
## Advanced Options
```gleam
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")
}
```
## API Overview
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>.
## Common Patterns
### Environment-Based Profiles
```text
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:
```sh
# Development
export YODEL_PROFILES=dev
gleam run
# Staging
export YODEL_PROFILES=staging
gleam run
# Production
export YODEL_PROFILES=prod
gleam run
```
### Feature-Based Profiles
Profiles aren't just for environments - use them to layer any configuration changes:
```text
config/
├── config.yaml # Base configuration
├── config-debug.yaml # Enable debug logging
├── config-metrics.yaml # Enable metrics collection
└── config-experimental.yaml # Enable experimental features
```
```sh
# Enable debug logging and metrics in production
export YODEL_PROFILES=prod,debug,metrics
gleam run
```
```gleam
// Or activate features programmatically
let assert Ok(config) =
yodel.default_options()
|> yodel.with_profiles(["debug", "metrics"])
|> yodel.load_with_options("./config")
```
### Database Configuration
```toml
# 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}"
```
```gleam
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),
)
}
```
## Development
```sh
gleam test # Run the tests
```
## License
This project is licensed under the Apache License 2.0.