If you've shipped Laravel apps, you already know 80% of Suprnova. This chapter maps your habits to the Rust equivalent so you can get productive fast. We'll show the patterns you reach for daily, the patterns that change shape, and the few things Rust gives you for free that PHP can't.
TL;DR side-by-side
| You wrote in Laravel | You write in Suprnova |
|---|---|
composer create laravel/laravel my-app |
suprnova new my-app --frontend svelte |
php artisan serve |
suprnova serve |
php artisan migrate |
suprnova migrate |
php artisan make:controller PostController |
suprnova make:controller post |
Route::get('/posts/{id}', [PostController::class, 'show']) |
get!("/posts/{id}", controllers::post::show) (in routes!) |
class Post extends Model |
#[suprnova::model] struct Post { … } |
Post::find($id) |
Post::find(id).await? |
Post::where('status', 'published')->get() |
Post::query().db_where("status", "published").get().await? |
Auth::user() |
Auth::user().await? |
Cache::remember('key', 60, fn() => …) |
Cache::remember("key", Some(Duration::from_secs(60)), || async { … }).await? |
Queue::push(new SendEmail($user)) |
Queue::push(SendEmail { user_id }).await? |
Mail::to($u)->send(new Welcome($u)) |
Mail::to(&u.email).send(WelcomeMail { user: u }).await? |
Storage::disk('s3')->put($path, $bytes) |
Storage::disk("s3")?.put(&path, bytes).await? |
Notification::send($u, new Invoice($i)) |
Notify::send(&u, &InvoiceNotification { invoice }).await? |
Gate::allows('update', $post) |
Gate::allows::<PostPolicy, _>("update", &user, &post).await? |
request()->validate([...]) |
#[handler] extracts an #[derive(Data, Validate)] arg directly |
event(new OrderShipped($order)) |
EventFacade::dispatch(OrderShipped { order }).await? |
Bus::dispatch(new ProcessFoo($x)) |
Bus::dispatch(ProcessFoo { x }).await? |
php artisan schedule:list |
suprnova schedule:list |
php artisan tinker |
(no REPL - write a one-off cargo run script or test) |
composer require league/csv |
cargo add csv |
The mental model shift
Async, everywhere
The biggest change: every database call, HTTP call, file I/O, cache call,
queue push - anything that crosses a boundary - is async and you call
it with .await?. Once you've done it for a few hours, it disappears
into the rhythm. Until then, the compiler will point at every spot you
forgot.
// Laravel
$user = find;
$user;
to;
// Suprnova
let user = find.await?;
user.subscribe.await?;
to.send.await?;
? is Rust's "early return on error". A handler returns
Result<HttpResponse, HttpResponse> (aliased as Response), so a ?
on a DB error short-circuits into your error converter and the client
gets a proper 500 (or 4xx, depending on the error kind). You almost
never have to write a try/catch - ? does it.
Compile-time models
Where Eloquent reads your DB schema at runtime, Suprnova reads it at compile time:
That's it - that struct IS the Eloquent model. You get
Post::find, Post::query(), Post::create, post.update(...),
post.delete(), soft deletes (with #[model(soft_deletes)]),
timestamps, observers, the works. The macro generates a SeaORM
Entity, Model, ActiveModel, and Column enum, and impls the
Suprnova Model trait - but you depend on Post, not any of those.
If you rename a column in a migration, the struct doesn't match the DB schema anymore - and depending on your config, either the compiler catches it at build time or the type-coerced cast fails on first query. Either way you find out before staging, not after.
Single binary
There's no PHP-FPM, no nginx config reading index.php, no composer install on deploy. cargo build --release gives you one statically
linked binary. scp it to a server, systemd it, done. Or build a
container - FROM scratch works.
We have deployment recipes for Railway, Digital Ocean, and Hetzner. The common shape: build the binary, ship the binary, set env vars, run.
Mapping the framework
Routes
routes! plays the role of routes/web.php and routes/api.php
combined.
use ;
use cratecontrollers;
routes!
Full reference: Routing. Differences worth knowing:
- Group middleware is flattened into each route's middleware list at register time (not run as a separate chain layer) - this means there's no extra runtime cost for grouping.
- Both Laravel's
{id}and Rails-style:idsyntax work; they're normalised internally. - Named routes resolve via
route("posts.show", &[("id", "42")])and there's a signed-URL variant for time-limited links.
Controllers
A controller is just a free function returning Response:
use ;
use cratePost;
pub async
You can also use the #[handler] macro to extract typed args (route
params, query, body, the request itself, container services) at the
signature:
use handler;
pub async
The post::Model type comes from the model's generated module - that's
the signal #[handler] uses to pick route model binding over the
default form-request extraction. If the row doesn't exist, the binding
returns a 404 before your code runs - same behaviour as Laravel's
implicit binding.
Action structs (single-method "invokable" controllers, Laravel-style) are supported too: see Actions.
Eloquent
The dual-API query builder takes either Laravel names or Rust-idiomatic names - both work, pick whichever reads cleanly at the call site.
// Laravel surface
let active = query
.db_where
.order_by_desc
.limit
.get
.await?;
// Rust surface (identical result)
let active = query
.filter
.order_by_desc
.take
.get
.await?;
db_where is the Laravel-side name (the bare where collides with the
Rust keyword). filter is the Rust-idiomatic alias. Both exist; both
do the same thing. For non-equality operators, reach for db_where_op
(or its filter_op alias): .db_where_op("status", "!=", "archived").
See the Eloquent reference - it's the longest chapter
for a reason, the surface is wide.
Auth
use ;
// In a handler:
let user = user.await?; // Option<Arc<dyn Authenticatable>>
let id = user.as_ref.map;
// Logging in (e.g. inside your login controller):
let creds = password;
attempt.await?;
// Logging out:
logout.await?;
Guards, providers, sessions, remember-me, email verification, password
reset, brute-force throttling, TOTP 2FA, and OAuth are all here. The
auth-flows surface mirrors Laravel Fortify. Email verification and
password reset are provider-backed (no torii required): your user model
implements MustVerifyEmail / CanResetPassword - the Suprnova
analogues of Laravel's contracts of the same names - and the configured
UserProvider drives the flows. See Authentication
and Auth Flows.
Migrations
You write SeaORM migrators. The shape will look familiar even if the syntax is new:
use *;
;
suprnova make:migration create_posts_table scaffolds the file.
suprnova migrate, migrate:rollback, migrate:status, migrate:fresh
all do what you'd expect. suprnova db:sync runs migrations and
regenerates the SeaORM entities the macro layer compiles against.
See Migrations.
Queues and scheduling
use ;
use ;
// Define a job - the data lives on the struct, the contract lives on
// `impl Job`.
// Push it onto the queue:
push.await?;
// Or with a delay:
later.await?;
Workers run with cargo run -- queue:work. Drivers include
memory and sync (in-process, for tests), database, redis, and null.
Batches, chains, unique jobs, retries, backoff, middleware, failed-job
store - all there. See Queues.
Scheduling uses the Task trait and the per-project scheduler binary:
use ;
;
// Register inside bootstrap (e.g. via Schedule::call / .task / .add):
// schedule.add(schedule.task(DailyDigest).daily().at("03:00").name("daily-digest"));
See Task Scheduling.
Mail, notifications, broadcasting
These follow Laravel one-to-one. Mailable is a derive macro;
Notifiable is a trait on your User model; channels are
mail/database/broadcast/webpush; broadcasting supports
public, private, and presence channels. See Mail,
Notifications, Broadcasting.
Frontend
There's no Blade. Instead, the frontend is a real SPA via Inertia.js, and you pass typed props from Rust:
use ;
pub async
Posts/Show is a Svelte component (or React, or Vue - your starter
picks). TypeScript types for the props are generated automatically from
the InertiaProps derive - run suprnova generate-types after adding a
new prop struct and the frontend gets typed bindings.
If you've used Inertia in Laravel via inertia(), this is the same
thing - just typed end-to-end. See the Frontend overview.
Things that change shape
A few things move differently in Suprnova. None of them are blockers, but they're worth knowing up front.
No service providers
Laravel has dozens of service providers registering bindings, observers,
view composers, etc. Suprnova has one bootstrap function in your
app's bootstrap.rs. You register everything there, in order. It's not
elegant but it's transparent - you can see in 30 lines exactly what
your app boots.
// bootstrap.rs
use Arc;
pub async
The Container and Bootstrap chapters have the detail.
Configuration is typed
Where Laravel uses config('app.timezone') returning whatever-the-array-says,
Suprnova has typed config structs:
let cfg = ?;
let tz = &cfg.timezone; // &str, not mixed
You can register your own typed config sections. See Configuration.
No facades-as-aliases
Laravel facades like DB:: are class-aliases configured in config/app.php.
Suprnova facades are real modules at the crate root:
use ;
Same surface, no global aliasing needed.
Compile times are real
Rust compile times are not PHP. A clean build of a fresh Suprnova app
takes 1–2 minutes; incremental builds during development are a few
seconds. The dev workflow is the same - suprnova serve watches for
changes and rebuilds - but you'll feel it the first time you change a
macro and recompile a downstream crate. Caching pays for itself fast.
The borrow checker exists
Most controllers and handlers never touch a lifetime annotation - the
framework's signatures hide them. When the borrow checker yells at you,
it's usually because you tried to hold a reference across an .await
that crossed a mutex or held a DB transaction across an awaited call
that needed exclusive access. The errors are clear and the fixes are
usually .clone() or restructure-into-smaller-scopes.
No tinker REPL
There isn't a REPL. The closest equivalent is a one-off cargo run
script in examples/, or a #[suprnova_test] test that exercises the
thing you're debugging. Most of what you'd do in tinker (poke at a
model, fire a notification, dispatch a job) is a 5-line test.
Where Laravel chapters land
Quick lookup if you know what you're after but not where it lives:
| Laravel topic | Suprnova chapter |
|---|---|
| Lifecycle | Request Lifecycle |
| Service Container | Service Container |
| Service Providers | Application Bootstrap |
| Facades | Service Container |
| Routing | Routing |
| Middleware | Middleware |
| CSRF Protection | CSRF Protection |
| Controllers | Controllers |
| Requests | Requests |
| Responses | Responses |
| URL Generation | URL Generation |
| Session | Session |
| Validation | Validation |
| Error Handling | Error Handling |
| Logging | Logging |
| Artisan Console | Console + CLI Reference |
| Broadcasting | Broadcasting |
| Cache | Cache |
| Events | Events |
| File Storage | File Storage |
| HTTP Client | HTTP Client |
| Localization | Localization - Fluent .ftl catalogs, not PHP arrays |
| Notifications | Notifications |
| Queues | Queues |
| Rate Limiting | Rate Limiting |
| Task Scheduling | Task Scheduling |
| Authentication | Authentication |
| Authorization | Authorization |
| Email Verification | Auth Flows |
| Password Reset | Auth Flows |
| Encryption | Encryption |
| Hashing | Hashing |
| Database | Database |
| Query Builder | Query Builder |
| Pagination | Pagination |
| Migrations | Migrations |
| Seeding | Seeding |
| Eloquent | Eloquent |
| Eloquent: Relationships | Relationships |
| Eloquent: Collections | Collections |
| Eloquent: Mutators / Casts | Mutators & Casts |
| Eloquent: API Resources | API Resources |
| Eloquent: Serialization | Serialization |
| Eloquent: Factories | Factories |
| Testing | Testing |
| HTTP Tests | HTTP Tests |
| Database Testing | Database Tests |
| Mocking | Mocking & Fakes |
| Cashier (Stripe) | Payments: Stripe |
| Cashier (Paddle) | Payments: Paddle |
| Sanctum / Passport | (not yet - token auth via torii integration) |
| Horizon | (not yet - queue introspection is built-in) |
| Telescope / Pulse | (deferred to v2+) |
Things Laravel has that Suprnova doesn't (yet):
- Telescope / Pulse (observability surface) - basic observability ships, the dashboards don't
- Sanctum / Passport token auth - torii integration covers OAuth and session auth; dedicated token auth is intended, not shipped
- Horizon - queue introspection is built into the framework, no separate dashboard
- Blade - by design; Inertia is the frontend story
trans_choice- Localization ships, but plurals are selected inside the message by CLDR category rather than by the[1,19]-style integer rangestrans_choicetakes
Next
- Installation - get a project running
- Quickstart - build a tiny app in 5 minutes
- Routing - the natural next chapter from here
Or jump anywhere via documentation.md.
