# Renox: 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. Docs: https://docs.renox.rs · Source: https://github.com/renox-rs/renox · Demo: https://bikeshop.renox.rs ## What's included - **Routing & requests**: Named routes, groups, domains, guards, rate limits, signed URLs, route model binding. Guide: https://docs.renox.rs/docs/routing - **Models & migrations**: A query builder that checks column names, relations without N+1, migrations with batches, SQLite or PostgreSQL. Guide: https://docs.renox.rs/docs/relations - **Validation**: Form requests as structs with Laravel's rules, hooks, live validation, errors placed next to the field. Guide: https://docs.renox.rs/docs/validation - **Auth & permissions**: Login, registration, reset, verification, API tokens, roles per tenant, policies, two-factor login, OAuth. Guide: https://docs.renox.rs/docs/authorization - **Queues & scheduler**: Jobs with retries, chains and batches; a cron scheduler that is safe across servers. No Redis needed. Guide: https://docs.renox.rs/docs/queue - **Mail & notifications**: SMTP with failover, mail views, notifications by mail, in the database and as a live stream. Guide: https://docs.renox.rs/docs/mail - **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. Guide: https://docs.renox.rs/docs/ui - **One-binary deploys**: Views, translations, migrations and assets compiled in; systemd socket activation, so restarts refuse no connection. Guide: https://docs.renox.rs/docs/operations ## How a request is handled - **The request arrives**: Axum and Tokio underneath: async, on every core, nothing to install next to the binary. - **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. - **Sessions**: An encrypted, signed cookie by default; a database driver when you outgrow it. Flash data, remember-me, per-device logout. - **Authentication**: The user is loaded once per request, from the session or a Bearer token with its abilities, with roles and permissions per tenant. - **CSRF**: Every unsafe method is checked: a hidden field for forms, the X-XSRF-TOKEN header for htmx and JSON. - **Your handler**: A plain async fn. Extractors validate the input before it runs; ? turns any error into the right response. - **Views**: MiniJinja templates, a UI kit of 70 components, htmx fragments and out-of-band swaps, error pages in your layout. - **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. ## Compared with Laravel, Loco and Axum | | Renox | Laravel | Loco | Axum + crates | |---|---|---|---|---| | Language | Rust | PHP | Rust | Rust | | Checked at compile time | yes | no | yes | yes | | Auth, roles, two-factor, OAuth | yes | with packages | auth only | no | | Queue and scheduler without Redis | yes | yes | yes | no | | UI kit and data grid | yes | with Filament | no | no | | Admin panel, billing, OAuth plugins | yes | with packages | no | no | | Deploys as one file | yes | no | yes | yes | | Wiring you write yourself | none | none | some | all of it | ## Quick start ```bash cargo install renox-cli rnx new shop && cd shop rnx serve ``` --- # Renox 1.0: stable, with a design system and one real example 2026-10-08 · https://renox.rs/blog/renox-1-0 Renox 1.0 is out. Install it with one line: ```bash cargo install renox-cli rnx new shop --starter && cd shop rnx serve ``` The `--version` you needed during the release candidates is gone. From 1.0 on, Renox follows [semantic versioning](https://docs.renox.rs/docs/stability): code that compiles on 1.0 keeps compiling on every 1.x. ## What 1.0 promises The release candidates were for finding what was wrong before the promise was made. Six of them went out in a week. Each was tested end to end from crates.io: a new app, its tests, the tutorial followed as a reader would. Then the work went into an example big enough to break things. It found bugs, and every one was fixed before 1.0. It found a performance problem too: [one change made Renox 3.3× faster](/blog/how-a-benchmark-made-renox-faster). ## A design system, taken from a real app [The bike shop](/blog/meet-the-bike-shop) needed parts the UI kit didn't have, so it built them. For 1.0 those parts moved into Renox, so every app gets them. - **A new default look.** The theme is called "editorial": a cream page, a teal accent with a terracotta second accent, large headlines, and pill buttons. Inside an app shell with a sidebar, the buttons stay compact. Every text colour passes WCAG AA in light and dark mode. The previous look is one attribute away: `data-rx-theme="warm"`. - **Icons.** `{{ icon("bike") }}` draws one of 112 Lucide icons, in the text's colour. Sidebar links, stat cards and empty states take an `icon=` too. - **A tab bar on phones.** `navbar(…, tabs=…)` turns the main sections into a bar at the bottom of a phone screen, and search moves behind a button. - **renox-blocks.** A new plugin with eleven components: - input: a quantity stepper, a two-handle range slider, a till's keypad, colour and size swatches, a date-and-time range; - display: a photo gallery, a timeline, a pricing table; - scheduling: a month calendar, an availability timeline and a kanban board. A page loads a component's code only when it uses that component. - **A lighter kit.** Charts, wizards, repeaters and tags now load only on the pages that have them. A plain page's script is 22% smaller. - **A starter that looks finished.** `rnx new --starter` now comes with a public home page, a dashboard with a welcome card and stat cards, and branded sign-in pages. They are built from page patterns that live in your app, so you can change them. ## One example that explains itself Renox used to ship sixteen small examples. Now it ships two: - **the bike shop**, the complete use case; - **hello**, the smallest app. Everything the other fourteen examples showed is now a page of the bike shop: - each store has a page on its own host; - [/about/fields](https://bikeshop.renox.rs/about/fields) shows every form input type with its Rust, SQLite and PostgreSQL types; - [/about/htmx](https://bikeshop.renox.rs/about/htmx) runs every htmx recipe live. Every page of the shop now explains itself **beside its own code**. On a wide screen, a panel sits next to the page. It says what the page is for and which Renox features it uses, and why. Below that are one to three code samples: the handler, the template, the query. These samples are cut from the real source files when the app is built, so they can't go stale, and a test fails if a page has none. [Open the shop](https://bikeshop.renox.rs) and pick any page. ## Upgrading from a release candidate Two changes since rc.6 can touch your code: - `AppState` is now one shared pointer, which made Renox 3.3× faster. Moving a field out of it no longer compiles: write `state.db.clone()` instead of `state.db`. The compiler points at each place. - The default currency is USD. Money in templates now takes the currency's smallest unit, as the data grid already did. If your app stores whole units, add `divide_by=1`. If it relied on the old default, set `APP_CURRENCY=IDR`. The [changelog](https://docs.renox.rs/docs/changelog) lists everything else. ## What's next 1.x adds without breaking. The next things we're working on are the remaining Laravel parity items, and faster sessions. The benchmark showed that the session layer is now the largest cost left on a bare route. If you build something with Renox, [tell us](https://github.com/renox-rs/renox/discussions). --- # How a benchmark made Renox 3.3× faster 2026-10-07 · https://renox.rs/blog/how-a-benchmark-made-renox-faster Before we put a single number on this site, we wanted to measure Renox properly against two things. The first is the floor it is built on: bare Axum, with the same crates and no middleware at all. The second is the framework it is modelled on: Laravel, both under PHP-FPM and under Octane. The [benchmark lives in the repository](https://github.com/renox-rs/renox/tree/main/benchmarks), so anyone can run it. The first run surprised us. ## The first numbers On two pinned CPU cores, 64 connections, a route that returns `Hello, World!`: | | Requests per second | p50 latency | |---|---:|---:| | Axum (bare) | 105,216 | 0.61 ms | | Renox | 9,265 | 7.16 ms | | Laravel Octane | 1,403 | 45.22 ms | | Laravel (PHP-FPM) | 722 | 88.96 ms | Renox was well ahead of Laravel, as you would expect. But it was an eleventh of the speed of bare Axum. Renox does more on every request than bare Axum: security headers with a CSP nonce, a request id, an encrypted session cookie, CSRF, the user, the locale, the view layer, error pages and a timeout. Even so, that work shouldn't cost 90% of the throughput. ## Queueing, or work? The first question was whether requests were waiting or working. With a single connection, Renox answered in 0.14 ms and bare Axum in 0.034 ms. So each request really did about four times as much work, and with 64 connections the two cores were simply full. ## Skipping the layers one by one We added a temporary switch that skips a chosen middleware layer, then measured with each layer skipped in turn: ```text none 9,049 rps guard 9,324 maintenance 11,032 view 11,165 session 10,981 auth 11,006 … all of them 65,181 ``` No single layer was slow. Each one gave back about 10 to 20 percent, and all of them together gave back seven times the throughput. Whatever the cost was, every layer paid it. ## What every layer does Each layer clones the application state. Axum's `from_fn_with_state` clones it for every call, it is put into the request's extensions, and each handler's `State` extractor clones it again. `AppState` was a plain struct of about 35 fields: the configuration, the database pool, the mailer, the queue, the cache, the route table, the translator and more. Most of them were `Arc`s, and one was a `Vec`. A small program timed it: ```rust for _ in 0..1_000_000 { std::hint::black_box(state.clone()); } ``` One clone took **382 ns** on one thread. With two threads cloning at the same time, it took **about 3 µs**. Every `Arc` clone is an atomic increment. When two cores increment the same counters, the cache lines holding them bounce between the cores, and 35 counters bounce together. A request did tens of clones, and as many drops. ## The fix `AppState` is now one `Arc` around its fields, and `Deref` makes reading them look exactly the same: ```rust #[derive(Clone)] pub struct AppState(Arc); impl std::ops::Deref for AppState { type Target = AppStateInner; fn deref(&self) -> &AppStateInner { &self.0 } } ``` `state.db`, `state.config` and `state.queue` all work as before. The only code that changes is code that moved a field out of a state, like `let db = state.db;` in a seeder. That now reads `let db = state.db.clone();`, and the compiler tells you so. ## After | | Before | After | |---|---:|---:| | `AppState::clone`, one thread | 382 ns | 12 ns | | `AppState::clone`, two threads | ~1.6 µs | 79 ns | | `/plaintext` | 9,240 rps | **30,535 rps** | | `/page`: 20 rows from SQLite as HTML | 6,310 rps | **9,935 rps** | That is **3.3× the requests** on a bare route. On a real page, with a query and a template, it is 1.6×, because there the database and the template do a larger share of the work. ## What's left Bare Axum is still about 3.5× faster on a route that does nothing. The remaining cost is spread over the layers again. The session and the maintenance check, which reads a file on every request, cost about 10 percent each. Those are the next things to look at. We'll keep the benchmark in the repository and rerun it as Renox changes. --- # Meet the bike shop: a whole business built with Renox 2026-10-07 · https://renox.rs/blog/meet-the-bike-shop Framework examples are usually small. A to-do list proves that a form can be saved. It doesn't prove that the framework holds up when a real business with real rules is built on it. So the flagship example of Renox is a business: [a bike shop with three stores](https://bikeshop.renox.rs). ## What it does The shop **sells** bikes, gear and parts. It has a catalogue with filters and search, product pages with variants and stock per store, a cart and a checkout, plus a point of sale for the counter. It **rents** bikes by the hour or the day. A timeline shows which bike is free when. Deposits are taken, a late return is charged, and a bike placed at another store is booked there. It **services** bikes. Customers book a slot in the workshop, mechanics work through a board, and customers approve quotes. Service plans book the visits for you every week or month. Behind the counter it **runs three stores**: - staff have roles in a given store, for a given time (a mechanic who helps another store this week); - stock is a ledger, and there are consignment goods and purchase orders; - the stores keep books between them, with a monthly settlement; - there are reports and exports, and an admin panel. ## Every page explains itself Each page has an **About this page** panel. Since Renox 1.0, it sits beside the page on a wide screen, with the code behind it: the handler, the template, the query, cut from the source when the app is built. It says what the page is for, who uses it, which Renox features it relies on and why, what happens under the hood, which guides to read and which source files to open. If you want to see how Renox does route model binding, rate limits, roles per tenant, live validation or a data grid with exports, there is a page that does it, and it tells you so. To try every side of the shop, log in with one of the demo accounts listed on [the login page](https://bikeshop.renox.rs/login): a customer, the owner, a manager, a cashier or a mechanic. ## What it is built with It uses Renox and Renox's plugins, and that's all: - **The UI kit** for every page. The storefront has a warm editorial theme set through the kit's own tokens, so the components follow it in light and dark mode. - **The data grid** for the staff lists, with filters, summaries, exports to CSV and Excel, and a print page. - **Roles per store**, so staff rights depend on the store they are working in. The code checks permissions, never role names. - **renox-2fa** for staff sign-in, **renox-billing** for service plans, **renox-admin** for the admin panel. - **The queue and the scheduler**: notifications, overdue rentals, monthly settlements. - **One binary** to deploy. The live demo is built by GitHub Actions, and the server pulls each new build. ## Read the code The bike shop lives in the repository at [examples/bikeshop](https://github.com/renox-rs/renox/tree/main/examples/bikeshop). Its README maps every feature to its page and its files. Start the app with `rnx serve`, open any page, and press **About this page**. --- # Why we built Renox 2026-10-07 · https://renox.rs/blog/why-we-built-renox Rust is a great language for web applications. It is fast, it uses little memory, and its compiler catches whole classes of bugs before a user ever sees them. Yet most teams that start a web app pick something else. The reason isn't the language. It's everything around it. ## Twenty crates and a week of glue A web app needs much more than a router. It needs sessions, CSRF protection, a template engine, a database layer, migrations, validation with messages a person can read, login and password resets, roles and permissions, background jobs, a scheduler, mail, file uploads, a cache, error pages, and a way to deploy it all. In Rust each of those is a crate, usually a good one. But you choose them, you read how each one wants to be wired, you write the glue, and you keep them in step as they release. When something doesn't fit, say a session store that wants a different async runtime, or a job queue that needs Redis when you wanted SQLite, the glue grows. Every team builds its own framework, and every one of them is a little different. Laravel solved this problem for PHP more than a decade ago. You install one thing and everything is there, it fits together, and the names are the same in every project. That is why people are productive in it on the first day. ## One dependency Renox is that idea for Rust. You add one crate: ```toml [dependencies] renox = "1" ``` and import one prelude: ```rust use renox::prelude::*; ``` Routing, sessions, CSRF, views, models and migrations, validation, auth with roles and permissions, a queue, a scheduler, mail, notifications, cache, storage, translations, webhooks, SEO tags and a UI kit are all there, tested together. You don't copy framework code into your app: your app depends on Renox, so an upgrade is a version bump. If you know Laravel, you already know the shape of it. There are routes with names, form requests (`Valid`), models with relations, jobs, mailables, policies and `artisan`-style commands. The difference is that the compiler now checks your work. The [Laravel → Renox guide](https://docs.renox.rs/docs/laravel) goes through it concept by concept. ## Choices we made **One runtime crate.** Everything lives in `renox-core`, re-exported by `renox`. Splitting it into an HTTP crate, a database crate and a view crate sounds tidy, but they all need the same application state, so they would all depend on each other. **Our own migrator and queue.** We wanted Laravel-style migration batches, migrations that modules own, and a queue that runs on the database you already have, SQLite or PostgreSQL, with retries, chains, batches and unique jobs. No Redis is required. **Sessions in a cookie by default.** The session is encrypted and signed, so a small app needs no session table. A database driver is one setting away when you outgrow it. **HTMX and Alpine.js, not a JavaScript build.** Pages are rendered on the server with MiniJinja. HTMX swaps fragments, and a validation error comes back as a 422 that Renox's own script places next to the field. There is no `node_modules` and no bundler. **One binary.** Views, translations, migrations and public files are compiled in. The binary is also its own CLI: `migrate`, `queue:work`, `schedule:work`, `db:seed`. You copy one file to a server, and with systemd's socket activation a restart doesn't refuse a single connection. ## Who it is for Renox is for people who want to build a product, not a framework: a SaaS, a back office, a shop, an internal tool. You get Rust's speed and its compiler, without first spending a week deciding how sessions should work. It is open source, MIT OR Apache-2.0, and close to 1.0. The best way to see what it can do is the [tutorial](https://docs.renox.rs/docs/tutorial). It builds one app from `rnx new` to deploy. Or open the [bike shop](https://bikeshop.renox.rs), a whole business built with Renox, where every page explains the features it uses.