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 known limits 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 with the typed ModelAdmin builder and the CRUD panel is already there — list view, search, filters, permissions, all of it:
use runique::admin::ModelAdmin;
use crate::entities::article;
fn articles() -> ModelAdmin<article::Entity> {
use article::Column::*;
ModelAdmin::new()
.title("Articles")
.list(|l| {
l.display([Title, Status, Views])
.links([Title])
.filter([Status])
})
.form(|f| f.fields([Title, Body, Status]))
}

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 | typed ModelAdmin builder |
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, and a form sent from another site is refused before the handler - 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, their content encrypted, 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
Prefer plain
cargo run. Wire the admin with.with_admin(...)and the typedModelAdminbuilder insrc/admin.rs— no code generation step (see Admin (beta)).
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 Routes, which the builder mounts on Axum:
pub fn routes() -> Routes {
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): 4.0.0.
CLI
runique gives you (see runique/src/bin/runique.rs):
runique new <name>runique create-superuserrunique test [file] [test]runique makemigrations --entities src/entities --migrations migration/src [--force false]runique migration up | down [-n N] | status(runs the project'smigration/crate withcargo run, nosea-orm-clineeded)
There is no runique start: the app is a normal Rust binary (cargo run). Admin is plain Rust (ModelAdmin + .with_admin(...)), with no generation step.
Admin (beta)
Admin is a typed builder — no macro, no generated files under src/admins/.
- declare each resource with
ModelAdmin::<Entity>::new()insrc/admin.rs - register them on
AdminSitevia.with_admin(admin::config)inmain - run with plain
cargo run
Columns are SeaORM enums (article::Column::Title), so a typo fails at compile time; a secret column (a password) put in a list or a form is refused at startup. Permissions, list filters, search and forms are configured on the same builder.
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 an encrypted database fallback.
Full reference: Sessions
Tests and coverage
- Tests reported: 3352 passing (110 ignored)
- Coverage snapshot (
2026-11-04, packagerunique, admin module included): functions 92.51%, lines 92.45%, regions 91.51%
cargo llvm-cov --package runique --features all-databases,admin,test-utils --summary-only
Full 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 — progress of the
makemigrationswork - Changelog
- Upgrading from 3.0 to 4.0
- Runique vs Django — feature comparison
- Crates.io
- Security policy
License
MIT — see LICENSE