Expand description
§Runique — the Django developer experience, in type-safe Rust
Declare a model once, and you get the database table, the migration, a type-safe form, and a full admin panel — no extra wiring. Runique brings Django’s productivity to Rust without asking you to give up Rust’s safety or performance. It’s built on Axum, SeaORM and Tera, and it stays out of your way once the boilerplate is gone.
Status, plainly: active development. The framework crate (
runique) is the source of truth;demo-appis a real application exercised against it, not a toy. The admin panel is in beta. Nothing below is dressed up — see the project status for the unfiltered version.
🌍 Languages: English | Français
§Declarative macros, not boilerplate
model! {
Article,
table: "articles",
pk: id => Pk,
enums: { Status: [Draft="Draft", Published="Published"], },
{
title: text [required],
slug: text [unique],
body: richtext [required],
status: choice [enum(Status), default: "Draft"],
views: int [default: 0],
}
}model! generates the SeaORM entity (article::Model) and its SQL migration (runique makemigrations) from the same declaration. Pair it with #[form] and you get a matching type-safe form, validated server-side and derivable straight from the schema. Register the resource in admin! and the CRUD panel is already there — list view, search, filters, permissions, all of it:
admin! {
article: article::Model => ArticleForm {
title: "Articles",
list_display: [["title", "Title"], ["status", "Status"], ["views", "Views"]],
list_filter: [["status", "Status", 5]],
}
}§Why Runique
Rust already has fast, low-level building blocks for the web — what it doesn’t have is a framework that gives you Django’s day-to-day productivity out of the box. Wiring an ORM, a template engine, a forms layer and an admin together yourself is a project of its own before you’ve written a single feature. Runique does that wiring for you, following one set of conventions, so the time goes into your app instead of your plumbing — and you keep Rust’s type safety and performance the whole way through.
| Django (Python) | Runique (Rust) |
|---|---|
models.py | model! → SeaORM entity + migration |
forms.py | #[form] type-safe forms |
admin.py | admin! generated admin panel |
urls.py | urlpatterns! routing macro |
| Django templates | Tera (auto-escaped) |
| QuerySet | SeaORM + search! query DSL |
| middleware | ordered middleware slots |
For the full picture: Runique vs Django.
§Security by default
None of this is bolted on afterward — it’s part of the base you start from:
- CSRF protection compares tokens in constant time (
ct_eq), so timing can’t leak a match - CSP ships with a per-response nonce, configurable through the builder
- Login is timing-safe (no user enumeration through response time) and passwords are hashed with Argon2
- Sessions persist to the database, with authenticated sessions protected first when memory runs low
- Password reset tokens live in the database hashed with SHA-256, single-use, and hardened against IDOR
- Output is sanitized (ammonia) on top of Tera’s own auto-escaping, and host validation is enforced
§Quick start
runique new myapp
cd myapp
cargo run # your app is a normal Rust binary
runique startregenerates the admin CRUD code from youradmin!declarations, then launchescargo runitself — it’s a one-shot generation step chained into the launch, not a background watcher (see Admin (beta)). Plaincargo runskips regeneration.
A trimmed-down main.rs (the full version lives in demo-app/src/main.rs):
use runique::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = RuniqueConfig::from_env();
let db = DatabaseConfig::from_env()?.build().connect().await?;
RuniqueApp::builder(config)
.routes(url::routes())
.with_database(db)
.statics()
.build()
.await
.map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?
.run()
.await?;
Ok(())
}Routes go through the urlpatterns! macro and come out as a regular Axum Router:
pub fn routes() -> Router {
urlpatterns! {
"/" => view!{ index }, name = "index",
"/blog/{id}" => view!{ blog_detail }, name = "blog_detail",
}
.rate_limit("/login", "login", view!(login_user), 10, 60, vec![Method::POST])
}For the full walkthrough: Installation
§What’s in this repository
runique/— the framework crate itself, the product and the source of truthdemo-app/— a real application built against the framework, used to validate itdocs/— documentation in English and French
Workspace version (source of truth): 3.0.2.
§CLI
runique gives you:
runique new <name>runique start [--main src/main.rs] [--admin src/admin.rs]— regenerates admin code, then launches the app (one-shot, not a watcher)runique create-superuserrunique makemigrations --entities src/entities --migrations migration/src [--force false]runique migration up|down|status --migrations migration/src
⚠️ A note on rolling back migrations
runique makemigrationswrites migrations that keep the chronological order of the migration system intact. If you ever need to roll one back, reach for the SeaORM CLI instead — it keeps the migration tracking table in sync with the schema’s actual state. Mixing the two rollback paths can desynchronize that tracking.
§Admin (beta)
runique start does three things, in order, on a single thread:
- parses your
admin!declarations insrc/admin.rs - generates the CRUD code under
src/admins/ - runs
cargo run --release, blocking
It checks for .with_admin(...) in src/main.rs first and only generates/launches if that’s present — otherwise it exits with a message telling you why. There’s no continuous watching: run runique start again to regenerate after editing src/admin.rs.
It’s still beta: permissions work mainly at the resource level for now, the generated src/admins/ folder gets overwritten on each regeneration, and hardening is ongoing rather than finished.
Admin docs: Admin
§Features and database backends
Enabled by default: orm, which brings SeaORM (entities, queries, migrations, sessions) but no database driver: a build with orm alone compiles, and makemigrations works, but the app can’t connect to anything.
Pick exactly one backend explicitly: sqlite, postgres, mysql, mariadb (mutually exclusive — enabling two at once is a compile error). Each one enables orm too, so features = ["postgres"] is enough. The all-databases feature stays available for multi-engine tooling that needs to talk to every backend at once.
§Sessions
CleaningMemoryStore stands in for the default MemoryStore, adding automatic cleanup of expired sessions, a two-tier watermark (128 MB / 256 MB) to keep memory bounded, and priority for authenticated sessions — they’re the last to be purged under pressure, and they survive restarts through a database fallback.
Full reference: Sessions
§Tests and coverage
- Tests reported: 2875 passing (110 ignored)
- Coverage snapshot (
2026-10-08, packagerunique, admin module included): functions 88.72%, lines 88.81%, regions 87.25%
cargo llvm-cov --package runique --features all-databases --summary-onlyFull per-file breakdown: docs/couverture_test.md
§Documentation
- Installation
- Architecture
- Configuration
- Routing
- Forms
- Model/Schema
- Templates
- ORM
- Middlewares
- Flash Messages
- Examples
- Admin beta
- Sessions
- Environment variables
§Project status & resources
- Project status — kept up to date as the project moves
- Changelog
- Runique vs Django — feature comparison
- Crates.io
- Security policy
§License
MIT — see LICENSE
Re-exports§
pub use forms::Prisme;pub use anyhow;pub use argon2;pub use async_trait;pub use axum;pub use chrono;pub use hmac;pub use regex;pub use runique_dsl;pub use sea_orm;pub use serde;pub use serde_json;pub use sha2;pub use tera;pub use tokio;pub use tower;pub use tower_http;pub use tower_sessions;pub use uuid;pub use dotenvy;pub use pulldown_cmark;
Modules§
- admin
- Admin module — administration interface: routes, configuration, reloading daemon, permissions, forms.
- app
- App module —
RuniqueAppBuilderconstructor, finalRuniqueAppapplication, build errors, and staging. - auth
- Authentication — session, guards, permissions, password reset.
- cli
- Runique CLI commands — new project, migration generation, server startup.
- config
- Application configuration — server, security, static files.
- context
- Request context — extractors, Request template, extensions, and Tera filters.
- db
- Database configuration and connection —
DatabaseConfigand SeaORM connection helper. - engine
- Runique engine —
RuniqueEnginecentralizes all shared dependencies (Tera, DB, config, registries). - errors
- Framework errors —
RuniqueError,ErrorContext,ErrorType, andRuniqueResult. - flash
- Flash messages — session storage, levels (success/error/info/warning), Axum extractor.
- forms
- Form system — fields, validation, Tera rendering, Prisme extractor, model forms.
- macros
- Framework macros — admin, DB (objects), Tera context, forms, router, templates.
- middleware
- Runique middlewares — security (CSP, CSRF, hosts), session, rate limit, error handling.
- migration
- Migration module — DSL definitions, schemas, columns, FKs, indexes, hooks, and utilities.
- prelude
- utils
- Cross-cutting framework utilities — type aliases, constants, i18n, password, CSRF, mailer…
Macros§
- admin
- context
- context_
update - define_
enum_ kind - Generates the
FieldKindenum with one variant per registered field type, plus aFrom<$field_type> for GenericFieldimpl for each variant so any field type can be wrapped into aGenericFieldwith.into(). - delegate_
to_ kind - error
- extend
- Declares a framework table extension — used by
makemigrationsto generate the correspondingALTER TABLE ADD COLUMNstatements. Has no effect at compile time. - flash_
now - impl_
form_ access - impl_
from_ error - impl_
objects - Adds a Django-style objects manager to a SeaORM entity.
- info
- model
- runique_
log - Emits a tracing event at the configured dynamic level.
- search
- search_
cond - success
- tpl
- tpls
- urlpatterns
- view
- warning
Structs§
- Lazy
Lock - A value which is initialized on the first access.