Configuration Files
Volga supports file-based application configuration. You can define settings in a TOML or JSON file, bind sections to strongly-typed Rust structs, and access them in request handlers via the Config<T> extractor. Hot-reload is also supported.
Prerequisites
Dependencies
If you're not using the full feature set, you need to enable the config feature in your Cargo.toml:
[dependencies]
volga = { version = "...", features = ["config"] }
serde = { version = "1", features = ["derive"] }
serde with the derive feature is required for deserializing config sections into your structs.
Supported Formats
Volga supports two configuration file formats (detected by file extension):
- TOML (
.toml) — recommended - JSON (
.json)
Quick Start
Create a config file app_config.toml in your project root:
[server]
host = "0.0.0.0"
port = 3000
[handler]
msg = "World"
Define a struct for your custom section and bind it during app setup:
use volga::{App, Config, ok};
use serde::Deserialize;
#[derive(Deserialize)]
struct HandlerConfig {
msg: String,
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut app = App::new().with_config(|cfg| {
cfg.with_file("app_config.toml")
.bind_section::<HandlerConfig>("handler")
});
app.map_get("/hello", |cfg: Config<HandlerConfig>| async move {
ok!(fmt: "Hello, {}!", cfg.msg)
});
app.run().await
}
Now a GET /hello request returns Hello, World!.
Loading Configuration
Volga provides three ways to load a config file:
Default Config
The simplest option — with_default_config() automatically discovers app_config.toml or app_config.json in the current working directory:
let app = App::new().with_default_config();
Warning
with_default_config() panics at startup if neither app_config.toml nor app_config.json is found.
Builder Closure
For full control, use with_config() which gives you a ConfigBuilder:
let app = App::new().with_config(|cfg| {
cfg.with_file("config/prod.toml")
.bind_section::<Database>("database")
.bind_section::<Cache>("cache")
.reload_on_change()
});
If you omit with_file(), the builder falls back to the same default file discovery as with_default_config() — it looks for app_config.toml or app_config.json in the current working directory. This is useful when you want the default file but also need to bind custom sections or enable hot-reload:
let app = App::new().with_config(|cfg| {
cfg.bind_section::<Database>("database")
.reload_on_change()
});
Standalone Builder
You can also create a ConfigBuilder separately and pass it via set_config():
use volga::{App, ConfigBuilder};
let config = ConfigBuilder::from_file("config/prod.toml")
.bind_section::<Database>("database")
.reload_on_change();
let app = App::new().set_config(config);
Binding Sections
Required Sections
Use bind_section::<T>(key) to register a required section. If the section is missing or malformed, the application panics at startup:
#[derive(Deserialize)]
struct Database {
url: String,
}
let app = App::new().with_config(|cfg| {
cfg.with_file("app_config.toml")
.bind_section::<Database>("database")
});
Corresponding app_config.toml:
[database]
url = "postgres://localhost/mydb"
Optional Sections
Use bind_section_optional::<T>(key) for sections that may be absent. If the section is missing, Config<T> will not be available, but the app won't panic:
#[derive(Deserialize)]
struct Cache {
ttl: u64,
}
let app = App::new().with_config(|cfg| {
cfg.with_file("app_config.toml")
.bind_section_optional::<Cache>("cache")
});
Accessing Config in Handlers
Use the Config<T> extractor to access a bound section in any request handler. It performs one atomic load + Arc::clone per request — no deserialization at runtime:
use volga::{App, Config, ok};
use serde::Deserialize;
#[derive(Deserialize)]
struct Database {
url: String,
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut app = App::new().with_config(|cfg| {
cfg.with_file("app_config.toml")
.bind_section::<Database>("database")
});
app.map_get("/db-url", |db: Config<Database>| async move {
ok!(db.url.as_str())
});
app.run().await
}
Built-in Sections
Volga automatically recognizes and applies certain reserved sections from the config file. These sections configure framework internals and do not require bind_section():
| Section | Feature Flag | Fields |
|---|---|---|
[server] | (always) | host, port, body_limit_bytes, max_header_count, max_connections |
[tls] | tls | TLS certificate configuration |
[tracing] | tracing | Tracing/logging configuration |
[openapi] | openapi | OpenAPI specification settings |
[cors] | middleware | CORS policy configuration |
For example, the [server] section lets you configure the host and port directly in the config file:
[server]
host = "0.0.0.0"
port = 8080
body_limit_bytes = 1048576
max_connections = 1000
Tips
Built-in sections are applied at startup only. They are not affected by hot-reload.
Hot Reload
Enable automatic config reloading with reload_on_change(). Volga will poll the config file every 5 seconds and update all bound sections:
let app = App::new().with_config(|cfg| {
cfg.with_file("app_config.toml")
.bind_section::<HandlerConfig>("handler")
.reload_on_change()
});
Reload behavior:
- Required sections — if a required section disappears from the file or becomes malformed during reload, the previous value is retained.
- Optional sections — if an optional section disappears,
Config<T>becomes unavailable. - Built-in sections (server, tls, etc.) are not reloaded — they are startup-only.
JSON Format Example
You can use JSON instead of TOML. Create app_config.json:
{
"server": {
"host": "0.0.0.0",
"port": 3000
},
"database": {
"url": "postgres://localhost/mydb"
}
}
let app = App::new().with_config(|cfg| {
cfg.with_file("app_config.json")
.bind_section::<Database>("database")
});
Full Example
For a complete working example, see the config example in the Volga repository.