Open source · MIT / Apache-2.0 · Rust 1.94+

Laravel's productivity.
Rust's guarantees.

Renox is a batteries-included web framework for Rust. Routing, models, migrations, validation, auth, queues, mail, a UI kit and more, behind one dependency, compiled into one binary.

Start the tutorial → cargo install renox-cli
  • 1 crate to depend on
  • 1 binary to deploy
  • 0 JavaScript build steps
❯ cargo install renox-cli
  Installed rnx v1.1.1
❯ rnx new shop
  created Cargo.toml
  created src/lib.rs
  created src/app/home/mod.rs
  created resources/views/layouts/app.html
  created migrations/
  created tests/home.rs
  created AGENTS.md
❯ cd shop && rnx serve
  Compiling shop v0.1.0
  migrated 9 tables
  ▲ Renox listening on http://127.0.0.1:3000

1,608tests

90%line coverage, enforced

72UI kit components

130demo pages explained beside their code

6official plugins

27built-in commands

001 The problem

A Rust web app today is twenty crates and a week of glue.

Rust gives you speed and a compiler that catches bugs before your users do. But for a web app you pick a router, an ORM, a session store, CSRF protection, a queue, a mailer, templates and migrations, then wire them together and keep them in step. Laravel solved that years ago, without Rust's guarantees. Renox brings that productivity to Rust.

  • One dependency. renox = "1" and use renox::prelude::*: everything else is already there, and already fits.
  • Laravel's ideas, Rust's types. Routes, models, form requests, jobs, mailables and policies: names you know, checked at compile time. Laravel → Renox, concept by concept.
  • Upgrades are a version bump. Apps depend on Renox; framework code is never copied into them.
  • axum
  • tower
  • sqlx
  • tokio
  • minijinja
  • serde
  • validator
  • argon2
  • lettre
  • cookie
  • tower-sessions
  • apalis
  • cron
  • tracing
  • reqwest
  • object_store
  • chrono
  • uuid
  • csrf
  • governor
  • rust-embed
  • clap

renox = "1.1"all of it, in one crate

002 Batteries included

Everything a real app needs. Already wired.

Not a starter template: a framework you depend on, tested as one piece on SQLite and PostgreSQL, on Linux, macOS and Windows.

  • 001

    Routing & requests

    Named routes, groups, domains, guards, rate limits, signed URLs, route model binding.

    Routes::new().get("/", home) Read the routing & requests guide →
  • 002

    Models & migrations

    A query builder that checks column names, relations without N+1, migrations with batches, SQLite or PostgreSQL.

    #[derive(Model)] Read the models & migrations guide →
  • 003

    Validation

    Form requests as structs with Laravel's rules, hooks, live validation, errors placed next to the field.

    Valid<NewOrder> Read the validation guide →
  • 004

    Auth & permissions

    Login, registration, reset, verification, API tokens, roles per tenant, policies, two-factor login, OAuth.

    .require_permission("orders.refund") Read the auth & permissions guide →
  • 005

    Queues & scheduler

    Jobs with retries, chains and batches; a cron scheduler that is safe across servers. No Redis needed.

    state.queue.dispatch(SendInvoice { … }) Read the queues & scheduler guide →
  • 006

    Mail & notifications

    SMTP with failover, mail views, notifications by mail, in the database and as a live stream.

    state.notify(user, &OrderShipped { … }) Read the mail & notifications guide →
  • 007

    UI kit, grid & HTMX

    72 accessible components, 112 icons, an editorial theme, a data grid with exports, and renox-blocks; htmx and Alpine.js built in.

    {{ grid(orders) }} Read the ui kit, grid & htmx guide →
  • 008

    One-binary deploys

    Views, translations, migrations and assets compiled in; systemd socket activation, so restarts refuse no connection.

    rnx build Read the one-binary deploys guide →

003 Under the hood

Every request goes through a pipeline you don't have to build.

Security headers, sessions, CSRF, auth and error pages are on from the first line. Hover a layer to see what it does.

  1. The request arrives

    Axum and Tokio underneath: async, on every core, nothing to install next to the binary.

    GET /orders/42 HTTP/1.1
    Host: shop.example.com
    Cookie: renox_session=…
  2. Security headers & CSP

    nosniff, frame options, HSTS and a Content-Security-Policy with a nonce per request, on every response. Trusted hosts and proxies too.

    content-security-policy: default-src 'self';
      script-src 'self' 'nonce-r4Nd0m'
    strict-transport-security: max-age=31536000
  3. Sessions

    An encrypted, signed cookie by default; a database driver when you outgrow it. Flash data, remember-me, per-device logout.

    session.flash("status", "Saved")?;
    session.put("cart", &cart)?;
  4. Authentication

    The user is loaded once per request, from the session or a Bearer token with its abilities, with roles and permissions per tenant.

    Routes::new()
        .get("/orders", index)
        .require_permission("orders.view")
  5. CSRF

    Every unsafe method is checked: a hidden field for forms, the X-XSRF-TOKEN header for htmx and JSON.

    <form method="post">
      {{ csrf_field() }}
      …
    </form>
  6. Your handler

    A plain async fn. Extractors validate the input before it runs; ? turns any error into the right response.

    async fn store(State(state): State<AppState>,
                   Valid(form): Valid<NewOrder>) -> Result<Redirect> {
        let order = form.into_order().insert(&state.db).await?;
        Redirect::route("orders.show", &[&order.id])
    }
  7. Views

    MiniJinja templates, a UI kit of 70 components, htmx fragments and out-of-band swaps, error pages in your layout.

    {% call card("Order #" ~ order.id) %}
      {{ infolist(order) }}
    {% endcall %}
  8. The response

    A full page, an htmx fragment, JSON, a download or a stream, with a request id, and an ETag when you ask for one.

    HTTP/1.1 200 OK
    x-request-id: W5-L3oxyBUY2tvYT
    content-type: text/html; charset=utf-8

004 Coming from Laravel?

You already know Renox. The compiler just checks your work now.

Routes

Laravelroutes/web.php
Route::get('/orders', [OrderController::class, 'index'])
    ->name('orders.index')
    ->middleware('auth');

Route::post('/orders', [OrderController::class, 'store'])
    ->name('orders.store')
    ->middleware(['auth', 'throttle:10,1']);
Renoxsrc/app/orders/mod.rs
Routes::new()
    .get("/orders", index).name("orders.index")
    .post("/orders", store).name("orders.store")
    .throttle(10, Duration::from_secs(60))
    .require_auth()

Models

Laravelapp/Models/Order.php
class Order extends Model
{
    use SoftDeletes;

    protected $fillable = ['customer_id', 'total'];

    public function customer()
    {
        return $this->belongsTo(Customer::class);
    }
}
Renoxsrc/app/orders/model.rs
#[derive(Model, Serialize, Default)]
#[model(soft_deletes)]
pub struct Order {
    pub id: i64,
    pub customer_id: i64,
    pub total: i64,
    pub deleted_at: Option<DateTime>,
}

// The customers of a page of orders, in one query:
let customers = belongs_to::<Customer, _, _>(
    &db, &orders, |o| o.customer_id,
).await?;

Validation

Laravelapp/Http/Requests/StoreOrder.php
class StoreOrder extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => 'required|email',
            'quantity' => 'required|integer|min:1',
        ];
    }
}
Renoxsrc/app/orders/form.rs
#[derive(Deserialize, Validate)]
pub struct StoreOrder {
    #[validate(required, email)]
    pub email: String,
    #[validate(required, min = 1)]
    pub quantity: i64,
}

async fn store(Valid(form): Valid<StoreOrder>) -> Result<Redirect> { … }

Jobs

Laravelapp/Jobs/SendInvoice.php
class SendInvoice implements ShouldQueue
{
    public $tries = 5;

    public function __construct(public int $orderId) {}

    public function handle(): void
    {
        Mail::to($this->order->email)->send(new Invoice($this->order));
    }
}

SendInvoice::dispatch($order->id);
Renoxsrc/app/orders/jobs.rs
#[derive(Serialize, Deserialize)]
pub struct SendInvoice { pub order_id: i64 }

impl Job for SendInvoice {
    const NAME: &'static str = "send-invoice";
    const MAX_ATTEMPTS: u32 = 5;

    async fn handle(self, ctx: JobContext) -> Result {
        let order = Order::find_or_404(&ctx.state.db, self.order_id).await?;
        ctx.state.mailer.send(invoice(&order)).await
    }
}

state.queue.dispatch(SendInvoice { order_id: order.id }).await?;

The whole Laravel → Renox guide, from routes to queues, with what is different and why.

005 Benchmarks

All those batteries, a tenth of Laravel's latency.

A real page: 20 rows from SQLite rendered through a template, with every Renox default on (CSP, session, CSRF, auth, locale, error pages). Renox serves 9× Laravel Octane and 16× PHP-FPM, at a tenth of the latency and a tenth of the memory, and keeps about two thirds of bare Axum's throughput.

Requests per second/page · higher is better
Requests per second, /page · higher is better
Axum (bare)15,137
Renox9,630
Laravel Octane1,066
Laravel FPM581
Median latency/page · lower is better
Median latency, /page · lower is better
Axum (bare)4.2 ms
Renox6.7 ms
Laravel Octane59.7 ms
Laravel FPM112 ms
Peak memory under loadlower is better
Peak memory under load, lower is better
Axum (bare)11 MiB
Renox12 MiB
Laravel FPM106 MiB
Laravel Octane128 MiB

Measured 2026-10-07 on an Intel i7-6700: each server pinned to 2 cores and 1 GB in Docker, oha with 64 connections for 15 s after a 5 s warm-up. The raw numbers and the bare-route results are in benchmarks/RESULTS.md. How it is measured, and how to run it yourself.

006 Deploy

Your whole app is one file. Copy it. Run it.

Views, translations, migrations and public files are embedded at build time. The binary is its own CLI too: migrate, queue:work, schedule:work, db:seed. No PHP-FPM, no node_modules, no runtime to keep patched. Running Renox in production.

007 Built with Renox

Not a hello world. A whole business.

The bike shop runs three stores that sell, rent and service bikes: checkout, rentals, a workshop, service plans, staff roles per store, reports. Every page explains which Renox feature it uses and why.

008 How it compares

The productivity of a full-stack framework, the footprint of a library.

FeatureRenoxLaravelLocoAxum + crates
LanguageRustPHPRustRust
Checked at compile timeyesnoyesyes
Auth, roles, two-factor, OAuthyeswith packagesauth onlyno
Queue and scheduler without Redisyesyesyesno
UI kit and data gridyeswith Filamentnono
Admin panel, billing, OAuth pluginsyeswith packagesnono
Deploys as one fileyesnoyesyes
Wiring you write yourselfnonenonesomeall of it

Three lines

Start your next app in Rust without giving up a thing.

$ cargo install renox-cli
$ rnx new shop && cd shop
$ rnx serve