Architecture of a Runique Project

Architecture of a Runique Project

A Runique project is a standard Rust binary crate. runique new generates the base structure, which you evolve as needed.

Project structure

my-project/
├── src/
│   ├── entities/          # Model declarations
│   │   ├── users.rs       # Uses Runique AST for the makemigrations CLI
│   │   └── blog.rs        # CLI is not compatible with plain structs
│   │
│   ├── formulaire/        # Form declarations
│   │   ├── inscription.rs # Using the form engine
│   │   └── blog.rs        # or proc-attribute macros
│   │
│   ├── main.rs            # Entry point — RuniqueApp builder
│   ├── admin.rs           # admin!{} declaration (if admin enabled, required for runique start)
│   ├── urls.rs            # urlpatterns! — routing table
│   ├── views.rs           # Handlers (async functions)
│   └── forms.rs           # RuniqueForm structs (or forms/ folder)
│
├── templates/             # Tera templates (.html)
├── static/                # Static files (CSS, JS, images)
│   └── media/             # Uploads (FileField)
│                          # media/ can be placed in another folder
│
├── migration/             # SeaORM migrations
│   └── src/
│       └── lib.rs
├── .env                   # Environment variables
└── Cargo.toml

Role of each file

main.rs — Configures and starts the application via the builder:

#[macro_use]
extern crate runique;
use runique::prelude::*;

mod entities;
mod formulaire;
mod urls;
mod views;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = RuniqueConfig::from_env();
    let db_config = DatabaseConfig::from_env()?.min_connections(1).build();
    let db: DatabaseConnection = db_config.connect().await?;

    password_init(PasswordConfig::auto());

    RuniqueApp::builder(config)
        .routes(urls::routes())
        .with_database(db)
        .statics()
        .build()
        .await?
        .run()
        .await?;

    Ok(())
}

urls.rs — Declares routes via urlpatterns!:

use runique::prelude::*;
use crate::views;

pub fn routes() -> Router {
    urlpatterns! {
        "/" => view!{ views::index }, name = "index",
        "/register" => view!{ views::register }, name = "register",
    }
}

views.rs — Request handlers:

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

formulaire/ — Typed forms:

pub struct RegisterForm { pub form: Forms }

impl RuniqueForm for RegisterForm {
    fn register_fields(form: &mut Forms) {
        form.field(&TextField::text("username").label("Username").required());
        form.field(&TextField::email("email").label("Email").required());
    }
    impl_form_access!();
}

admin.rs — Admin view declaration (files generated into src/admins/):

admin! {
    users: users::Model => RegisterForm {
        title: "Users",
        permissions: ["admin"],
    }
}

Starting a new project

runique new my-project
cd my-project
runique start

runique new generates the minimal structure above. What to edit and what to leave alone:

File / folderEdit?Role
src/main.rsYesConfigures the builder and declares modules
src/urls.rsYesRouting table
src/views.rsYesRequest handlers
src/entities/YesModel declarations (Runique AST, compatible with makemigrations)
src/formulaire/YesForms and validation
src/admin.rsYes (if admin)admin!{} declaration, required for runique start
src/admins/NoGenerated by the daemon — do not edit manually
templates/YesTera templates
static/YesCSS, JS, images
migration/No (unless adding tables)SeaORM migrations
.envYesEnvironment variables

runique start watches src/admin.rs and regenerates src/admins/ on every change. For apps without an admin view, cargo run is sufficient.


Framework internal structure

runique/src/
├── app/                    # App Builder, Templates & Intelligent Builder
│   ├── builder.rs          # RuniqueAppBuilder with slots
│   ├── error_build.rs      # Build errors
│   ├── templates.rs        # TemplateLoader (Tera)
│   └── staging/            # Staging structs
│       ├── core_staging.rs
│       ├── middleware_staging.rs
│       └── static_staging.rs
├── config/                 # Configuration & Settings
├── context/                # Request Context & Tera tools
│   ├── request.rs          # Request struct (extractor)
│   └── tera/               # Tera filters and functions
├── db/                     # ORM & Database
├── engine/                 # RuniqueEngine
├── errors/                 # Error handling
├── flash/                  # Flash messages
├── forms/                  # Form system
├── macros/                 # Utility macros
│   ├── context_macro/      # context!, context_update!
│   ├── flash_message/      # success!, error!, info!, warning!, flash_now!
│   └── router/             # urlpatterns!, view!, impl_objects!
├── middleware/             # Middleware (Security)
│   └── security/           # CSRF, CSP, Host, Cache, Error Handler
├── utils/                  # Utilities
├── lib.rs
└── prelude.rs

SectionContent
Core ConceptsRuniqueEngine, Request, Prisme<T>
MacrosContext, flash, routing, and error macros
Tera Tags & FiltersDjango-like tags, filters, functions
Middleware StackSlot order and dependency injection
Request LifecycleLifecycle and best practices