Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting started

Requirements

  • Rust 1.97.1 or newer (edition 2024). rust-toolchain.toml pins stable.
  • PostgreSQL 17 for anything using the database or the job queue.
  • Node.js, only if you are building a frontend with Vite. Arcature itself publishes no npm package.

Installing

cargo add arcature

That is the whole install. To follow main ahead of a release instead, depend on the repository and pin a revision – a branch reference will move under you.

[dependencies]
arcature = { git = "https://github.com/ArcatureLabs/Arcature", rev = "..." }

The smallest application

use arcature::application::EngineResult;
use arcature::prelude::*;

#[arcature::main]
async fn main() -> EngineResult<()> {
    Application::new()
        .routes(Routes::new([Route::get("/", index).name("home")]))
        .build()
        .run()
        .await
}

async fn index() -> Result<Response> {
    Ok(text(StatusCode::OK, "hello"))
}

Three things to notice.

.build() is required. Application::new() returns an ApplicationBuilder; .run() lives on Application. Forgetting .build() is a type error, not a runtime surprise.

run() returns EngineResult<()>, not the framework’s Result<()>, and EngineResult is not in the prelude — it lives at arcature::application::EngineResult. Engine failures (a port already bound, a database that will not connect) are a different kind of failure from a handler’s, and they deliberately do not share an error type.

Handlers return Result<Response>, where Result is Arcature’s. text, json, redirect and no_content build the common shapes.

The generated application

arc new writes a Laravel-shaped project rather than a single file:

app/
  controllers/   models/     services/
  requests/      policies/   resources/
bootstrap/
  app.rs         state.rs
config/
database/migrations/
routes/mod.rs
resources/js/    resources/css/
public/
storage/
src/main.rs      src/lib.rs
tests/smoke.rs
.env

bootstrap/app.rs is the composition root. It loads .env, reads typed configuration, and wires the subsystems:

pub fn app() -> Result<Application<crate::bootstrap::AppState>> {
    dotenvy::dotenv().ok();
    let config = crate::config::load()?;
    Ok(Application::new()
        .routes(crate::routes::routes())
        .bind(&config.bind_addr)
        .port(config.port)
        .database(config.database)
        .cache(config.cache)
        .storage(config.storage)
        .mail(config.mail)
        .jobs(jobs_registry())
        .build())
}

bootstrap/state.rs defines AppState, the cloneable bundle every handler reaches through State<AppState>. Each field is an Option, because a subsystem that was never configured contributes None rather than a panic:

#[derive(Clone)]
pub struct AppState {
    pub db: Option<Db>,
    pub jobs: Option<Jobs>,
    pub cache: Option<Cache>,
    pub storage: Option<Storage>,
    pub mail: Option<Mailer>,
}

The state is produced after startup, from the started Resources, which is why it is a closure rather than a value:

pub fn state_fn() -> Arc<dyn Fn(&Resources, &Lifecycle) -> AppState + Send + Sync> {
    Arc::new(|res, _lc| AppState {
        db: res.db().cloned(),
        jobs: res.jobs().cloned(),
        cache: res.cache().cloned(),
        storage: res.storage().cloned(),
        mail: res.mail().cloned(),
    })
}

src/lib.rs puts the two together with run_with_state.

Features

Arcature’s features reduce the compile surface; they are not a self-assembly kit. default is a working full-stack application. Turn features off to compile less, not on to reach a usable state.

# The whole framework.
arcature = { git = "...", features = ["fullstack"] }

# An API server: no Inertia, no static assets pipeline.
arcature = { git = "...", default-features = false, features = ["api", "database", "auth", "validation"] }

The database driver is split three ways — db-postgres, db-sqlite, db-mysql — so a SQLite application does not compile the PostgreSQL protocol. The job queue requires PostgreSQL.

Next

Your first module to add a feature to the application you just generated, Routing for how requests reach handlers, or Inertia if you are building a page-driven frontend.