Cookbook / Data and storage

Configuration from the environment

Load typed settings from environment variables and a .env file, with every problem reported at once.

config::from_env fills a struct from environment variables, so you get typed settings and a startup error that lists everything that is wrong, rather than a crash on the first missing value.

src/main.rs
use axeon::{config, Json, Server};
use axeon::json;
use serde::Deserialize;

#[allow(dead_code)] // hand these to your database and cache clients
#[derive(Deserialize)]
struct Config {
    database_url: String,         // DATABASE_URL
    port: u16,                    // PORT
    allowed_origins: Vec<String>, // ALLOWED_ORIGINS=a.com,b.com
    redis_url: Option<String>,    // optional
}

fn main() {
    config::load_dotenv().expect("invalid .env");
    let config: Config = config::from_env().expect("invalid configuration");

    let addr = format!("0.0.0.0:{}", config.port);
    let origins = config.allowed_origins.clone();

    let mut app = Server::new();
    app.state(config);
    app.get("/config", move |_req| {
        let origins = origins.clone();
        async move { Json(json!({ "origins": origins })) }
    });
    app.listen(&addr).expect("server failed to start");
}
Terminal
# .env
DATABASE_URL=postgres://localhost/app
PORT=3000
ALLOWED_ORIGINS=https://a.example.com,https://b.example.com

Notes#

  • Field names map to upper-case variables: database_url reads DATABASE_URL.
  • Comma-separated values fill a Vec; an Option field may be absent.
  • Real environment variables win over .env, so production settings are never overridden by a stray file.
  • Keep secrets (JWT keys, database passwords) here, never in source.