<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: itsuki</title>
    <description>The latest articles on DEV Community by itsuki (@seballiot).</description>
    <link>https://dev.to/seballiot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2477061%2F2de41c2a-9349-49ef-bc0f-1db5a7fead31.png</url>
      <title>DEV Community: itsuki</title>
      <link>https://dev.to/seballiot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seballiot"/>
    <language>en</language>
    <item>
      <title>I Built a Django-Inspired Web Framework in Rust — Here's What I Learned</title>
      <dc:creator>itsuki</dc:creator>
      <pubDate>Sun, 14 Jun 2026 14:18:48 +0000</pubDate>
      <link>https://dev.to/seballiot/i-built-a-django-inspired-web-framework-in-rust-heres-what-i-learned-5bbf</link>
      <guid>https://dev.to/seballiot/i-built-a-django-inspired-web-framework-in-rust-heres-what-i-learned-5bbf</guid>
      <description>&lt;p&gt;If you've ever used Django, you know the feeling: one command, and you have an admin panel, an ORM, form validation, middleware, session handling — everything just works. Then you try Rust web development, and you're back to assembling pieces yourself.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;Runique&lt;/strong&gt;: a batteries-included web framework for Rust, inspired by Django's philosophy, built on top of Axum and Tokio.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why "Django for Rust"?
&lt;/h2&gt;

&lt;p&gt;Existing Rust frameworks are excellent at what they do — Axum is fast and composable, Actix-Web is a performance beast — but they're low-level by design. You bring your own ORM, your own session store, your own CSRF protection, your own admin interface. For many projects, that's the right tradeoff.&lt;/p&gt;

&lt;p&gt;Runique takes the opposite bet: &lt;strong&gt;convention over configuration&lt;/strong&gt;, with a structured, opinionated setup that gets you from zero to a production-ready app fast, without sacrificing Rust's safety and performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Builder: A Validated Construction Pipeline
&lt;/h2&gt;

&lt;p&gt;The part I'm most proud of is the application builder. You declare your components with a fluent API — in any order — and then a single &lt;code&gt;.build()&lt;/code&gt; call runs a &lt;strong&gt;fixed, validated construction pipeline&lt;/strong&gt; at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Validation → DB connection → Templates → Engine → Admin → Middleware → Static files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's what a typical app setup looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;password_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PasswordConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;auto_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Manual&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Argon2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;set_lang&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;En&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RuniqueConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RuniqueConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DatabaseConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DatabaseConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db_config&lt;/span&gt;&lt;span class="nf"&gt;.connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nn"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_log&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="nf"&gt;.dev&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.host_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;WARN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.acme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.with_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_mailer_from_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="py"&gt;.with_password_reset&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BuiltinUserEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="nf"&gt;.forgot_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"auth/forgot_password.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.reset_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"auth/reset_password.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.statics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.middleware&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="nf"&gt;.with_session_memory_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.with_session_cleanup_interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.with_allowed_hosts&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="nf"&gt;.enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_debug&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="nf"&gt;.host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"runique.io"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;.host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"www.runique.io"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;.host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;.host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1:3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="nf"&gt;.with_csp&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="nf"&gt;.policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SecurityPolicy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="nf"&gt;.with_header_security&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;.with_upgrade_insecure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_debug&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="nf"&gt;.scripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"'strict-dynamic'"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.with_admin&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nf"&gt;.site_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Administration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.sitemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://runique.io/sitemap.xml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;RuniqueAdminAuth&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="nf"&gt;.routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;admins&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/admin-runique/"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;.templates&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="nf"&gt;.with_dashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin/test_dashboard.html"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;.with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;admins&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;admin_state&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="nf"&gt;.with_rate_limiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.max_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.retry_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;.with_login_guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;LoginGuard&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.max_attempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.lockout_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;.page_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;.build()&lt;/code&gt; call validates every component — including cross-dependencies — before constructing anything. If your &lt;code&gt;SECRET_KEY&lt;/code&gt; is still the default insecure value in production, the build &lt;strong&gt;fails with a clear error and a fix suggestion&lt;/strong&gt;, before a single request is served:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Security] SECRET_KEY is using the default insecure value
  → Set SECRET_KEY to a random 32+ character string in your .env file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the Django &lt;code&gt;check&lt;/code&gt; framework equivalent, but enforced at startup — not discovered in production.&lt;/p&gt;


&lt;h2&gt;
  
  
  Security Included by Default
&lt;/h2&gt;

&lt;p&gt;Security in Runique isn't a plugin you add later. It's part of the construction pipeline itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSP (Content Security Policy)&lt;/strong&gt; — configurable profiles, per-request nonce, HTMX hash merging when the admin panel is enabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSRF protection&lt;/strong&gt; — built into the middleware stack, with per-route exemptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host validation&lt;/strong&gt; — allowed hosts checked before routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted proxies&lt;/strong&gt; — explicit configuration, not implicit trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security headers on static files&lt;/strong&gt; — &lt;code&gt;X-Content-Type-Options&lt;/code&gt;, &lt;code&gt;Strict-Transport-Security&lt;/code&gt;, &lt;code&gt;X-Frame-Options&lt;/code&gt;, &lt;code&gt;Referrer-Policy&lt;/code&gt; applied automatically
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You don't wire ServeDir yourself — `.statics()` does it,&lt;/span&gt;
&lt;span class="c1"&gt;// already wrapping every static/media route with security headers:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="s"&gt;"path"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// → X-Content-Type-Options: nosniff&lt;/span&gt;
&lt;span class="c1"&gt;//   Strict-Transport-Security: max-age=31536000; includeSubDomains; preload&lt;/span&gt;
&lt;span class="c1"&gt;//   X-Frame-Options: DENY&lt;/span&gt;
&lt;span class="c1"&gt;//   Referrer-Policy: strict-origin-when-cross-origin&lt;/span&gt;
&lt;span class="c1"&gt;//   + cache-control&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The middleware stack uses &lt;strong&gt;numbered slots&lt;/strong&gt; to guarantee application order — you can't accidentally apply CSRF before sessions, because the slots enforce the correct sequence.&lt;/p&gt;


&lt;h2&gt;
  
  
  What's Included
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ORM&lt;/strong&gt; (via SeaORM, optional feature flag)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template engine&lt;/strong&gt; (Tera, with custom filters and a URL registry for named routes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin panel&lt;/strong&gt; (auto-generated from your models, merged before the middleware stack so it always has session/auth context)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form engine&lt;/strong&gt; (typed structs with validation, v2 in progress)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session handling&lt;/strong&gt; (memory-first store with database fallback, built on &lt;code&gt;tower-sessions&lt;/code&gt;, with periodic cleanup)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n&lt;/strong&gt; (the FR/EN bilingual doc site is itself built with Runique)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleware system&lt;/strong&gt; with ordered slots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password reset&lt;/strong&gt; (pluggable, routes registered automatically during build)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug error page&lt;/strong&gt; — a styled page with collapsible sections and copy-to-clipboard for stack traces&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  One Concrete Example: Admin Merge Order
&lt;/h2&gt;

&lt;p&gt;Here's a subtle design decision that illustrates Runique's philosophy. In Axum, &lt;code&gt;.layer()&lt;/code&gt; only applies to routes present at call time. This means if you merge your admin router after applying middleware, the admin routes run &lt;strong&gt;without&lt;/strong&gt; session, CSRF, or extension context — a silent, hard-to-debug failure.&lt;/p&gt;

&lt;p&gt;Runique handles this in the builder:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 4b: admin + password reset — merged BEFORE the middleware stack.&lt;/span&gt;
&lt;span class="c1"&gt;// `.layer()` in Axum only covers routes present at call time;&lt;/span&gt;
&lt;span class="c1"&gt;// merging after means admin routes run without Session/CSRF/Extensions.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.admin.enabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin_router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Step 5: middleware applied AFTER, covering everything including admin&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="nf"&gt;.apply_to_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You don't have to think about this. The pipeline handles it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;Runique is at &lt;strong&gt;v2.1.x&lt;/strong&gt;, MIT licensed, with bilingual documentation (EN/FR) at &lt;a href="https://runique.io" rel="noopener noreferrer"&gt;runique.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The framework is used in production for a restaurant management system — dogfooding at its most direct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's missing&lt;/strong&gt; (being honest here, Django-style):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No async task queue yet (background jobs run on bare &lt;code&gt;tokio::spawn&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Email sending is partial&lt;/li&gt;
&lt;li&gt;OAuth is planned but not yet implemented&lt;/li&gt;
&lt;li&gt;Test coverage is at ~61% (growing)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Add the dependency to your &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;runique&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://runique.io/docs/en" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read the Getting Started Guide&lt;/a&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  Links &amp;amp; Resources
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/seb-alliot" rel="noopener noreferrer"&gt;
        seb-alliot
      &lt;/a&gt; / &lt;a href="https://github.com/seb-alliot/runique" rel="noopener noreferrer"&gt;
        runique
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A framework web base on Django/python
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Runique — the Django developer experience, in type-safe Rust&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/2da29fd375231aa4a1c7aa88bc1c9cf0f8dff75a6ef0f92cc18c7f804c8ca9c0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d312e39342532422d6f72616e6765"&gt;&lt;img src="https://camo.githubusercontent.com/2da29fd375231aa4a1c7aa88bc1c9cf0f8dff75a6ef0f92cc18c7f804c8ca9c0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d312e39342532422d6f72616e6765" alt="Rust"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/f9c93e573de524725c10bdeba536f89713262698b21c58ec380dffcb250299ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3233383025324225323070617373696e672d677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/f9c93e573de524725c10bdeba536f89713262698b21c58ec380dffcb250299ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3233383025324225323070617373696e672d677265656e" alt="Tests passing"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e" alt="License"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a17db639524d6818ecafa7c69ab34bf2077bcdc1a520485c61f37fc07d6ebeba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d322e312e31382d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/a17db639524d6818ecafa7c69ab34bf2077bcdc1a520485c61f37fc07d6ebeba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d322e312e31382d626c7565" alt="Version"&gt;&lt;/a&gt;
&lt;a href="https://crates.io/crates/runique" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2681e8d70b5efe4826f355b9f62a280f65466ed3b0605c911fa5b3082112b5c4/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f72756e69717565" alt="Crates.io"&gt;&lt;/a&gt;
&lt;a href="https://runique.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/96fcf63b7ce9ce7b9f5585864aa409f4ab1eea14f531e708f3be479d0ac07b9f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52756e697175652d627269676874677265656e" alt="Runique"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Declare a model once — get the database table, the migration, a type-safe form &lt;em&gt;and&lt;/em&gt; a full admin panel.&lt;/strong&gt; Runique is a batteries-included web framework that brings Django's productivity to Rust, without giving up Rust's safety and performance. Built on Axum, SeaORM and Tera.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status — honest:&lt;/strong&gt; active development. The framework crate (&lt;code&gt;runique&lt;/code&gt;) is the source of truth; &lt;code&gt;demo-app&lt;/code&gt; is a real validation app exercised against it. The admin is in &lt;strong&gt;beta&lt;/strong&gt;. Nothing below is overstated — see &lt;a href="https://github.com/seb-alliot/runique/blob/main/docs/en/PROJECT_STATUS.en.md" rel="noopener noreferrer"&gt;Project status&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🌍 &lt;strong&gt;Languages&lt;/strong&gt;: English | &lt;a href="https://runique.io/readme/fr" rel="nofollow noopener noreferrer"&gt;Français&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Declarative macros, not boilerplate&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-rust notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;model&lt;/span&gt;&lt;span class="pl-en"&gt;!&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-v"&gt;Article&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    table&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;"articles"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    pk&lt;span class="pl-kos"&gt;:&lt;/span&gt; id =&amp;gt; &lt;span class="pl-v"&gt;Pk&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    enums&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-v"&gt;Status&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-v"&gt;Draft&lt;/span&gt;=&lt;span class="pl-s"&gt;"Draft"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-v"&gt;Published&lt;/span&gt;=&lt;span class="pl-s"&gt;"Published"&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        title&lt;span class="pl-kos"&gt;:&lt;/span&gt;  text &lt;span class="pl-kos"&gt;[&lt;/span&gt;required&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
        slug&lt;span class="pl-kos"&gt;:&lt;/span&gt;   text &lt;span class="pl-kos"&gt;[&lt;/span&gt;unique&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/seb-alliot/runique" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Documentation: &lt;a href="https://runique.io" rel="noopener noreferrer"&gt;runique.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Crates.io: &lt;a href="https://crates.io/crates/runique" rel="noopener noreferrer"&gt;crates.io/crates/runique&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What do you think?
&lt;/h2&gt;

&lt;p&gt;I'm particularly curious to hear from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Django developers&lt;/strong&gt; — Does this structure feel familiar, or "too much" for the Rust ecosystem?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust experts&lt;/strong&gt; — How would you handle the middleware ordering differently?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's discuss in the comments!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>django</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
