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
Four published crates — one umbrella, one forced-separate proc-macro crate, one optional auth module, and one standalone CLI binary.
┌──────────────────┐ │ oxide-cli │ scaffolder, migrate, vendor:publish └────────┬─────────┘ │ shells out ▼ ┌──────────────────┐ │ your app │ consumer project └────────┬─────────┘ │ depends on ┌────────────┴────────────┐ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ oxide-auth │ │ oxide │ │ Sanctum tokens, │ │ core, config, │ │ Hash, abilities, │──────│ http, db all in │ │ middleware gates │ │ one umbrella │ └──────────────────┘ └────────┬─────────┘ │ re-exports ▼ ┌──────────────────┐ │ oxide-macros │ #[controller], │ (proc-macros) │ #[injectable], │ │ #[request], etc. └──────────────────┘Published crates
oxide
The umbrella. Ships four internal modules:
oxide::core— TypeId-keyed IoC container,ServiceProvidertrait,FromContainerfor dependency-injected constructors.oxide::config— TOML loader with shell-style env interpolation (${VAR:-default}), process-wideConfigsingleton, free helpers likeconfig("app.name"), auto type-coercion.oxide::http— the HTTP stack.Application, routing (hyper 1 + matchit),FromRequestextractors,Middlewaretrait and global middleware chain,Request/Response, validation envelopes, form requests, CORS, pagination.oxide::db— database layer on SeaORM.Databasehandle with lazy pool,DatabaseServiceProvider, Blueprint-shape migration DSL,Modelfacade, globalconn()accessor,timestamps_behavior!()macro.
Transparently re-exports every macro from oxide-macros, so
consumers only declare oxide in their Cargo.toml.
oxide-auth
Sanctum-style API tokens, kept in its own crate because it pulls
bcrypt + crc32fast + subtle + sha2 — projects that don’t need auth
shouldn’t compile that. Opt-in: add oxide-auth = { git = ... }
to your Cargo.toml and register AuthServiceProvider in
bootstrap.rs; see Authentication.
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, auth_middleware::<U>(),
abilities([...]), ability([...]) middleware gates.
oxide-macros
Proc-macros: #[controller(prefix = "...")], #[api_resource],
#[injectable], #[request], #[get / post / put / patch / delete]
route markers. Separate crate because Rust requires proc-macros to
live in a dedicated crate with proc-macro = true. Re-exported by
oxide so consumers never depend on it directly.
oxide-cli
The oxide binary. new scaffolder, make:migration,
vendor:publish, forwards serve / migrate / migrate:rollback /
migrate:fresh / migrate:status to the consumer project.
Install once, globally, with cargo install oxide-cli — the
binary is named oxide and is project-aware.
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.