Architecture of a Runique Project

Key Concepts

1. RuniqueEngine

The application's main shared state:

pub struct RuniqueEngine {
    pub db: Arc<DatabaseConnection>,
    pub tera: Arc<Tera>,
    pub config: Arc<RuniqueConfig>,
}

Injected as an Axum Extension, accessible in every handler via request.engine.


2. Request — The Main Extractor

Request is Runique's central extractor. It replaces the former TemplateContext and contains everything required:

pub struct Request {
    pub engine: AEngine,       // Arc<RuniqueEngine>
    pub session: Session,      // tower-sessions session
    pub notices: Message,      // Flash messages
    pub csrf_token: CsrfToken, // CSRF token
    pub context: Context,      // Tera context
    pub method: Method,        // HTTP method
}

Usage inside a handler:

pub async fn index(mut request: Request) -> AppResult<Response> {
    context_update!(request => {
        "title" => "Home",
    });
    request.render("index.html")
}

Methods:

  • request.render("template.html") — Render with the current context
  • request.is_get() / request.is_post() — Check the HTTP method

3. `request.form()` — Integrated Form Extraction

pub async fn handler(mut request: Request) -> AppResult<Response> {
    let mut form: RegisterForm = request.form();
    if request.is_post() && form.is_valid().await {
        let user = form.save(&request.engine.db).await?;
        success!(request.notices => "User created!");
        return Ok(Redirect::to("/").into_response());
    }

    context_update!(request => {
        "form" => &form,
    });
    request.render("form.html")
}

request.form() automatically:

  1. Parses the request body
  2. Creates a form instance
  3. Verifies the CSRF token
  4. Fills in submitted data