Rustango docs
← Cookbook

Chapter 9d — tenant_router for tenancy projects (v0.30, #80)

5 live tests against ViewSet::for_model(...).tenant_router(...) mounted under a real TenantContext extension with header-based tenant resolution. Run with DATABASE_URL=... cargo test --test cookbook_chapter09d_viewset_tenant_router -- --test-threads=1.

  • §9.116 — paginated list against the per-request tenant connection. → tenant_router_lists_paginated_payload
  • §9.116 — ?search=… ILIKE narrowing matches count to results (regression guard for the v0.30.1 CountQuery.search fix). → tenant_router_search_param_narrows_count_and_results
  • §9.116 — ?{field}=… exact filter via filter_fields. → tenant_router_filter_param_exact_match
  • §9.116 — full CRUD round-trip (POST → GET → PUT → DELETE → GET 404). → tenant_router_full_crud_round_trip
  • §9.116 — missing x-org header → 404 from the Tenant extractor before any SQL runs. → tenant_router_missing_header_yields_404_not_500

Why a separate router builder

router(prefix, pool) bakes a single pool at mount time — fine for single-tenant projects, broken for multi-tenant ones. Schema-mode tenants share the registry pool but rely on a per-checkout SET search_path, and database-mode tenants live in entirely separate Postgres databases. Mounting a normal ViewSet against &pool from inside a tenant project hits the wrong schema/database on every request.

tenant_router(prefix) solves this by resolving the connection per request via the Tenant extractor:

let posts_router = ViewSet::for_model(Post::SCHEMA)
    .filter_fields(&["author_id"])
    .search_fields(&["title", "body"])
    .ordering(&[("published_at", true)])
    .page_size(20)
    .permissions_for_model::<Post>()
    .tenant_router("/api/posts");        // no pool!

axum::Router::new().merge(posts_router)

v0.30 unification: every builder knob that worked for router(...) now works identically for tenant_router(...) — including permissions (the has_perm check runs against the same per-request connection, no second pool acquire). The earlier v0.27 v1 of tenant_router was filter-less and perm-less; that limitation is gone.

Sub-sections 9.114 (full pagination — count + next + prev), 9.116b (typed permissions), 9.117 (OpenAPI auto-derive), 9.118 (response shaping via .fields(&[...])) queued for Slice 9b.