Skip to content

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 changes
config/ *.toml files (env-interpolated)

Request lifecycle

  1. hyper accepts a TCP connection, builds an http::Request.
  2. server::handle reads the body (capped by APP_BODY_MAX_MB) and wraps it in an oxide_http::Request.
  3. Wraps the whole flow in tokio::time::timeout so handlers can’t pin a worker forever.
  4. Runs the global middleware chain (CORS, anything added via app.use_middleware(...)) — outer → inner.
  5. 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.
  6. Route-specific middleware chain runs next (auth, abilities, …).
  7. Handler runs. If it takes a typed FromRequest arg, the extractor validates; failure short-circuits with a 422 envelope.
  8. Response bubbles back up through every wrapping middleware layer so they can decorate headers (Cors, LogRequests, etc.).
  9. 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.