This version is in beta. Some features may change before release.

Your first app

Boot an umbral app with Settings, a SQLite pool, and one route.

The minimal umbral app boots through one builder, registers a database pool, and serves a hand-written route. From there you grow into models, migrations, and plugins.

The shape

Code
rust
use umbral::prelude::*;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let settings = umbral::Settings::from_env()?;
let pool = umbral::db::connect(&settings.database_url).await?;
 
let app = App::builder()
.settings(settings)
.database("default", pool)
.routes(Routes::new().get("/", || async { "hello, umbral" }))
.build()?;
 
app.serve("127.0.0.1:8000".parse()?).await?;
Ok(())
}

App::builder().build() runs five phases under the hood: collect, detect the backend, publish the ambient state, run system checks, and merge plugin routers. From the caller's side it's a single line.

Settings

Settings::from_env() layers defaults, an optional umbral.toml, and UMBRAL_-prefixed environment variables (last wins). The fields that matter on day one:

database_url

The DB connection string. Default sqlite::memory:, fine for tests but not for the management CLI. Override with UMBRAL_DATABASE_URL.

secret_key

Set via UMBRAL_SECRET_KEY. The dev default is rejected in production.

Warning
The `sqlite::memory:` default doesn't persist between subcommand invocations, so `cargo run -- migrate` quietly targets a throwaway database. Export `UMBRAL_DATABASE_URL=sqlite://app.db?mode=rwc` for any real work.

What's next

The full builder shape lives in docs/specs/01-app-and-settings.md.