Architecture
Oxide is composed of focused crates, each with a narrow responsibility. Together they form a cohesive, Laravel-style stack on top of Rust.
Crate graph
┌──────────────┐ │ oxide-cli │ scaffolder, migrate, vendor:publish └──────┬───────┘ │ shells out ▼ ┌──────────────┐ │ your app │ consumer project └──────┬───────┘ │ depends on ┌───────────────────────┴────────────────────────┐ │ │┌──┴──────────┐ ┌────────────────┐ ┌────────────┴───┐│ oxide-auth │ │ oxide-macros │ │ oxide-http ││ Sanctum, │ │ #[controller],│ │ hyper 1, ││ Hash, │ │ #[injectable],│ │ router, ││ abilities │ │ #[request] │ │ middleware │└──┬──────────┘ └────────┬───────┘ └────────┬───────┘ │ │ │ └────────────┬───────────┴────────────────────┘ │ ▼ ┌────────────────────┐ ┌─────────────────┐ │ oxide-db │ │ oxide-config │ │ SeaORM, Blueprint │ │ TOML + env │ │ migrations, Model │ │ interpolation │ └─────────┬──────────┘ └─────────┬───────┘ │ │ └──────────────┬──────────────┘ ▼ ┌────────────┐ │ oxide-core │ IoC container, │ │ service providers └────────────┘What each crate does
oxide-core
Foundation. TypeId-keyed IoC container, ServiceProvider trait,
FromContainer for dependency-injected constructors. Everything else
depends on this.
oxide-config
TOML loader with shell-style env interpolation (${VAR:-default}),
process-wide Config singleton, free helpers like config("app.name"),
auto type-coercion for bool / int / float when a value is one
${...} expression.
oxide-http
The HTTP stack. Application, routing (hyper 1 + matchit), handlers
with typed FromRequest extractors, Middleware trait + global
middleware chain, Laravel-shape Request / Response, validation
envelopes, form requests, CORS, pagination. Re-exports service provider
traits from oxide-core.
oxide-db
Database layer on top of SeaORM. Database handle with lazy pool,
DatabaseServiceProvider, Blueprint-shape migration DSL
(Schema::create(m, "todos", |t| { t.id(); })), Model facade with
zero-argument helpers (Todo::find_id(42).await), global conn()
accessor, timestamps_behavior!() macro.
oxide-auth
Sanctum-style API tokens. PersonalAccessToken entity published into
your project via vendor:publish sanctum. Authenticatable +
HasApiTokens traits for any user entity. Auth helper for
verification, Hash::make / Hash::check bcrypt wrappers
(BCRYPT_ROUNDS-compatible with Laravel), auth_middleware::<U>(),
abilities([...]), ability([...]) middleware.
oxide-macros
Proc-macros: #[controller(prefix = "...")], #[api_resource],
#[injectable], #[request], #[get / post / put / patch / delete]
route markers. Generates the glue between your controller impls and
oxide-http’s handler machinery.
oxide-cli
The oxide binary. new scaffolder, make:migration,
vendor:publish, forwards serve / migrate / migrate:rollback /
migrate:fresh / migrate:status to the consumer project.
Layer conventions inside your app
src/├── main.rs entry point + subcommand dispatch├── bootstrap.rs which service providers to register + in what order├── config.rs typed config structs (bind via bind_config::<T>)├── providers/ each provider wires one concern (config, DB, routes, …)├── routes/ one file per URL surface├── app/│ ├── domain/<name>/ entities + domain services (DDD-style)│ └── http/│ ├── controllers/│ ├── middleware/│ ├── requests/ FormRequest-shape validators│ └── resources/ API resource transformers└── database/migrations/ Migrator with all versioned schema changesconfig/ *.toml files (env-interpolated)Request lifecycle
- hyper accepts a TCP connection, builds an
http::Request. server::handlereads the body (capped byAPP_BODY_MAX_MB) and wraps it in anoxide_http::Request.- Wraps the whole flow in
tokio::time::timeoutso handlers can’t pin a worker forever. - Runs the global middleware chain (CORS, anything added via
app.use_middleware(...)) — outer → inner. - Terminal dispatcher looks up the route. Missing path → 404; wrong method → 405. Both still go through the global chain on the way out so CORS decorates them.
- Route-specific middleware chain runs next (auth, abilities, …).
- Handler runs. If it takes a typed
FromRequestarg, the extractor validates; failure short-circuits with a 422 envelope. - Response bubbles back up through every wrapping middleware layer so they can decorate headers (Cors, LogRequests, etc.).
- hyper serializes and flushes to the socket.
Graceful shutdown
SIGTERM / SIGINT flips a broadcast flag. The accept loop exits, new
connections are refused. Each in-flight connection gets told to
graceful_shutdown() — it completes its current request, then closes.
APP_SHUTDOWN_TIMEOUT_SECS caps how long stubborn connections get
before the process exits anyway.