It's Friday evening. You have an idea you can't shake and a weekend with nothing on it. The idea is a small SaaS — a dashboard your customers log into, each seeing only their own data. Nothing exotic. The kind of thing you've built before and remember mostly as boilerplate: wiring auth, hand-rolling an admin, writing the same CRUD endpoints, gluing a job queue on.
This time you reach for Umbral, a batteries-included framework for Rust — think the productivity of Django or Rails, on top of Rust's compile-time guarantees. Here's how the weekend goes.
Friday night: declare your data
You start a project and an app, then write the one thing that actually matters — your model.
That's not just a table. It's your migration, your admin screen, your form, and — if you want it — your REST resource. You declare the shape of your data once, and every other system reads that same declaration. No DTOs to keep in sync, no serializer that drifts from the model.
Two commands and the schema is real:
You go to bed having written a struct and gotten a database.
Saturday morning: the parts you'd normally dread
You wire up the plugins. This is the part that usually eats a Saturday; here it eats a coffee.
builder
.plugin
.plugin
.plugin
.plugin
.plugin
.build?;
Auth gives you a user model, argon2 hashing, and login/logout routes. OAuth adds "Sign in with Google/GitHub" — and reads its keys from the environment, so a provider with no credentials just isn't registered. Admin generates a full control panel for every model you declared: list views, search, filters, relation pickers, the works. REST turns those same models into a JSON API, safe-by-default (writes are refused until you grant permission).
By lunch you have a login page, a social login button, an admin you didn't design, and an API you didn't write. You spent your effort on the model, and everything else fell out of it.
Saturday afternoon: the multi-tenant part
Your whole idea hinges on isolation — customer A must never see customer B's projects. This is the part that keeps people up at night, so you let the framework carry it.
You scope every REST endpoint to the caller's own rows:
default
.resource
Now a list request only ever returns the projects that belong to whoever's asking. For defence in depth on Postgres, you turn on row-level security so the database itself enforces the boundary — even a bug in your handler can't read across it. Multi-tenancy, the thing you were dreading, is a couple of lines and a plugin.
Sunday: the moving parts
Real apps do work off the request path. A customer clicks "export," and you don't want them staring at a spinner while you build a PDF.
async
Enqueue it from a handler, run cargo run -- worker, and it drains in the background with retries. On Postgres the queue uses FOR UPDATE SKIP LOCKED, so you can run ten workers and each grabs a different job instead of fighting over the same one — the queue scales sideways for free.
Then you sprinkle in the finishing touches: file uploads for avatars through a storage backend that's local in dev and S3 in prod with the same code; a live toast when something happens, pushed over SSE; a /healthz endpoint so your deploy target stops guessing whether you're up.
Sunday night
You didn't build a framework this weekend. You built your app — and the framework quietly handled auth, the admin, the API, migrations, isolation, and background work, each as an opt-in plugin you could swap or drop.
The batteries-included promise was never "you can't do this yourself." Of course you can. The promise is that you shouldn't have to spend a weekend on the parts that are the same in every app — so you can spend it on the part that's only in yours.
Ship it. It's still Sunday.