App Builder
Builder pattern to configure and launch. Connects the Database, ServerConfig, Middleware pipeline, and async startup hooks.
use floz::prelude::*;
#[floz::main]
async fn main() -> std::io::Result<()> {
App::new()
.config(Config::from_env()) // loaded automatically if omitted
.server(ServerConfig::new()
.with_default_port(8080)
.with_middleware(RequestTrace::default())
.with_middleware(Compression::gzip())
)
.on_start(|ctx| async move {
// 1. Cache pre-warming
// 2. Spawning tokio::spawn background workers
// 3. Third-party handshakes (Consul, Redis)
info!("Worker loop initialized");
})
.run().await // auto-discovers #[route]s
}
AppContext
The injection container passed automatically to route handlers:
pub struct AppContext {
pub db_pool: DbPool,
pub config: Config,
pub extensions: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
Configuration
All config loaded from environment variables. No YAML, no TOML.
| Env Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | ✓ | — | PostgreSQL connection string |
HOST | 127.0.0.1 | Server bind host | |
PORT | 3030 | Server bind port | |
SERVER_ENV | DEV | DEV / STAGING / PROD | |
REDIS_URL | — | Redis (workers) | |
JWT_TOKEN | — | JWT signing secret | |
ECHO | — | Debug echo logging |
let config = Config::from_env(); // loads .env automatically
let config = Config::global(); // singleton
if config.is_dev() { /* dev-only */ }
let key = Config::require("MY_API_KEY"); // panics if missing
Zero-Cost Middleware
floz features a static-dispatch chained middleware pipeline instead of dynamically boxed futures. This allows the compiler to fully inline requests yielding zero overhead.
ServerConfig::new()
.with_middleware(Cors::permissive())
.with_middleware(RequestTrace::default())
.with_middleware(CacheMiddleware::default())
.with_middleware(Compression::gzip().level(3))
New: CacheMiddleware provides high-performance response caching orchestrated perfectly with your #[route(cache(...))] rules. RequestTrace adds structured logger spans for duration tracking. Compression applies GZip formatting based on client Accept-Encoding seamlessly.
Error Handling
Structured error types with automatic conversions from sqlx, serde, JWT, Redis, UUID, and anyhow.
// Automatic From conversions:
let err: ApiError = sqlx_error.into(); // 30+ variants mapped
let err: ApiError = serde_json_err.into(); // → BadRequest
let err: ApiError = jwt_error.into(); // → specific JWT error
// Convenience constructors
ApiError::bad_request("Invalid email");
ApiError::not_found("User not found");
ApiError::forbidden("Insufficient permissions");
ApiError::internal("Something went wrong");