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: 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.method.is_safe() — Check the HTTP method (GET/HEAD)

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

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

    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