tenaxum
Tenant-scoped helpers for Axum + sqlx + Postgres. Tenacious about row-level isolation.
tenaxum exposes two patterns. Pick whichever fits your codebase.
Pattern 1 — pool-scoped (recommended for production apps)
Set app.tenant_id once when a connection is checked out of the pool, reset it on release. Every query during the request is auto-isolated. Zero per-call-site boilerplate.
use ;
use PgPoolOptions;
use pool;
let pool = with_tenant_hooks
.connect.await?;
let app = new
// ...routes...
.layer;
Your auth layer inserts Extension<TenantId>; the tenant_scope middleware reads it and scopes a task-local; the pool hooks read the task-local on every connection checkout. Handlers just write sqlx::query!(...).fetch_one(&pool) and isolation happens automatically.
Pattern 2 — explicit begin_tenant
For background jobs, scripts, and admin paths where the pool hooks aren't wired:
use ;
let mut tx = pool.begin_tenant.await?;
let rows: = query_as
.fetch_all.await?;
tx.commit.await?;
What tenaxum does NOT do
- JWT decoding. Every app does it differently. Decode in your own middleware, then
req.extensions_mut().insert(tenaxum::TenantId(uuid)). - RLS policy SQL. See the examples directory for the full pattern with
FORCE ROW LEVEL SECURITY+ non-superuser-role +WITH CHECK. - Scope/permission middleware. Coming in v0.2.
How this was built
tenaxum was written by Claude acting as my coding agent, and was extracted from a production-shape Rust SaaS backend after a multi-agent code audit caught the exact RLS gotcha the proof tests in this repo demonstrate. API design is mine; implementation is Claude's. The library you're reading is the second draft — the first didn't fit the codebase it was extracted from, and plugging it in surfaced the mismatch immediately. See the workspace README for the full story.
If you'd rather not depend on AI-authored code, that's a reasonable position — close the tab, no hard feelings. If you do depend on it, the proof tests are there so you can verify the invariants yourself.
License
MIT.