Skip to main content

On This Page

Building Production-Ready Multi-Tenant SaaS in Rust with Actix-web

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Building Production-Ready Multi-Tenant SaaS in Rust with Actix-web

Developer Chinedu built SmartFarmAI to manage poultry operations across Nigeria and Tanzania. The platform employs PostgreSQL Row-Level Security (RLS) to prevent data leaks between farms, ensuring enterprise-grade isolation for operations with up to 60,000 birds.

Why This Matters

In multi-tenant architectures, relying solely on application-level logic like manual WHERE clauses creates a high risk of catastrophic data leaks due to human error. By shifting isolation enforcement to the database layer via PostgreSQL RLS, developers create a structural failsafe that ensures data remains private even if application-layer filters are omitted.

For SmartFarmAI, this approach balances the management simplicity of a shared database with the security guarantees of isolated schemas. This technical strategy significantly reduces operational overhead while maintaining the high level of trust required in agricultural business management.

Key Insights

  • PostgreSQL Row-Level Security (RLS) enforces data isolation at the database layer, removing the need for manual ‘WHERE org_id = $1’ clauses in application code.
  • SmartFarmAI uses composite foreign keys (org_id, farm_id) to make it structurally impossible to associate a batch with a farm from a different organization.
  • Actix-web middleware extracts tenant IDs from JWT claims and uses ‘SET LOCAL app.current_tenant_id’ to scope transactions to a specific tenant.
  • Using ‘SET LOCAL’ with the third argument set to ‘true’ ensures the tenant context is transaction-specific, preventing connection pool contamination between requests.
  • Infrastructure tables like Outbox queues must bypass RLS to allow background workers to poll across all organizations before switching to a per-tenant context for processing.

Working Examples

Schema implementation using composite foreign keys and PostgreSQL RLS policies.

CREATE TABLE batches (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  org_id UUID NOT NULL,
  farm_id UUID NOT NULL,
  CONSTRAINT batch_farm_fk FOREIGN KEY (org_id, farm_id) REFERENCES farms (org_id, id) ON DELETE CASCADE
);

ALTER TABLE farms ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON farms
USING (org_id = current_setting('app.current_tenant_id')::uuid);

Actix-web middleware logic to set the PostgreSQL transaction-local tenant context.

pub async fn set_rls_context(
    pool: &PgPool,
    org_id: &TenantId,
) -> Result<sqlx::Transaction<'_, sqlx::Postgres>, Error> {
    let mut tx = pool.begin().await.map_err(|e| {
        actix_web::error::ErrorInternalServerError(format!("DB error: {}", e))
    })?;

    sqlx::query("SELECT set_config('app.current_tenant_id', $1, true)")
        .bind(org_id.to_string())
        .execute(&mut *tx)
        .await?;

    Ok(tx)
}

Practical Applications

  • SmartFarmAI isolates egg production and mortality logs for distinct farms using SQLx and Actix-web. Pitfall: Using session-scoped session variables instead of transaction-local ones can leak data to the next user using that pooled connection.
  • Background workers process event queues by polling a global outbox table. Pitfall: Enabling RLS on infrastructure tables like work queues causes workers to return zero results because they lack a default tenant context.
  • Database migrations are performed by superusers to bypass RLS restrictions. Pitfall: Assuming RLS is active during migrations can lead to logic errors in scripts that require global data access.

References:

Continue reading

Next article

Automating Terraform Security Scans with Checkov and Azure Pipelines

Related Content