Home > Database > Building Multi-Tenant SaaS Applications with FastAPI, PostgreSQL, and Redis for Scalable Backends

Building Multi-Tenant SaaS Applications with FastAPI, PostgreSQL, and Redis for Scalable Backends

Building Multi-Tenant SaaS Applications with FastAPI, PostgreSQL, and Redis for Scalable Backends

Multi-tenant SaaS applications are widely used to serve multiple customers (tenants) with a single application instance. By leveraging technologies like FastAPI, PostgreSQL, and Redis, you can build scalable, high-performing backends that efficiently handle multiple tenants while ensuring data isolation and quick response times. This blog post will guide you through the process of creating such a backend, complete with explanations, examples, and code snippets.

Why Multi-Tenant SaaS Architecture?

Multi-tenancy allows developers to create a single application instance that serves multiple customers (tenants). Each tenant can have isolated data, configurations, and resources, reducing infrastructure costs while simplifying maintenance.

Key Benefits:
  1. Cost Efficiency: Shared infrastructure lowers costs.
  2. Scalability: Easier to scale horizontally or vertically.
  3. Simplified Maintenance: Updates and patches apply to all tenants simultaneously.
Technology Choices FastAPI

FastAPI is a modern, fast web framework for Python that supports asynchronous programming and type hints. It is ideal for building scalable APIs with minimal effort.

PostgreSQL

PostgreSQL is a powerful relational database system that provides advanced features like schemas and row-level security, making it perfect for multi-tenant architectures.

Redis

Redis is an in-memory key-value database often used for caching, session management, and pub/sub messaging, which enhances application performance.

Multi-Tenant Architecture Patterns 1. **Database Per Tenant**

Each tenant has its own isolated database, ensuring maximum data isolation but potentially increasing overhead on infrastructure.

2. **Schema Per Tenant**

Tenants share the same database, but each tenant’s data is stored in separate schemas. PostgreSQL’s schema support makes this approach highly efficient.

3. **Shared Database, Shared Tables**

All tenants share the same tables, with data distinguished using tenant IDs. This approach requires careful indexing and tenant-specific querying.

For this tutorial, we’ll use **Schema Per Tenant** since it balances isolation and resource efficiency.

Setting Up FastAPI, PostgreSQL, and Redis Step 1: Install Dependencies

Install the required Python libraries:

pip install fastapi uvicorn sqlalchemy psycopg2 redis
Step 2: Configure PostgreSQL

Create a PostgreSQL database and set up schemas for tenants. For example:


CREATE DATABASE multi_tenant_db;

-- Create schemas for tenants
CREATE SCHEMA tenant1;
CREATE SCHEMA tenant2;

Step 3: Define the FastAPI Application

Below is a basic FastAPI application setup:


from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import redis

# FastAPI app setup
app = FastAPI()

# PostgreSQL database connection
DATABASE_URL = "postgresql://user:password@localhost/multi_tenant_db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Redis connection
redis_client = redis.StrictRedis(host="localhost", port=6379, decode_responses=True)

# Dependency to get DB session
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Step 4: Tenant Isolation with Schemas

Use PostgreSQL schemas to isolate tenant data. Below is an example of how you can dynamically set the schema for a tenant:


from sqlalchemy import text

def set_tenant_schema(db, tenant_id: str):
    schema_name = f"tenant{tenant_id}"
    db.execute(text(f"SET search_path TO {schema_name}"))

Step 5: Cache with Redis

Redis can be used for tenant-specific caching. For example:


@app.get("/cache/{tenant_id}/{key}")
def get_cached_data(tenant_id: str, key: str):
    redis_key = f"{tenant_id}:{key}"
    value = redis_client.get(redis_key)
    if value:
        return {"key": key, "value": value}
    raise HTTPException(status_code=404, detail="Key not found")

Example: CRUD Operations for Tenants

Below is an example of creating a resource for a tenant and storing it in their schema:


@app.post("/tenant/{tenant_id}/resource")
def create_resource(tenant_id: str, resource_data: dict, db=Depends(get_db)):
    set_tenant_schema(db, tenant_id)
    
    # Example SQL for inserting data into tenant-specific schema
    db.execute(text(f"""
        INSERT INTO resources (name, description)
        VALUES (:name, :description)
    """), resource_data)
    db.commit()
    return {"message": "Resource created successfully"}

Scaling Your Application Horizontal Scaling

– Use a load balancer (e.g., Nginx) to distribute traffic across multiple instances of your FastAPI application.

Caching with Redis

– Store frequently accessed data in Redis to reduce database load.

Database Connection Pooling

– Configure connection pooling with SQLAlchemy to improve database performance.

Conclusion

Building a multi-tenant SaaS backend requires careful planning for tenant isolation, scalability, and performance optimization. By leveraging FastAPI for the API layer, PostgreSQL for data storage, and Redis for caching, you can create a robust, scalable backend tailored to handle multiple tenants efficiently. This tutorial covered the core concepts and provided practical examples to help you get started.