Multi-Tenancy in FastAPI: A Complete Guide
Building modern SaaS applications often requires serving multiple customers (tenants) from the same application while keeping their data securely isolated. This concept is called multi-tenancy. In this article, we will dive deep into multi-tenancy in FastAPI, explore different approaches, and implement a practical example to understand it better.
What is Multi-Tenancy? Multi-tenancy is an architectural pattern where a single application serves multiple customers (tenants). Each tenant might represent a company, department, or user group.
There are two main approaches to multi-tenancy:
- Database-per-tenant
- Each tenant has a completely separate database.
- Provides strong isolation.
- Harder to scale when the number of tenants grows.
2. Shared-database with tenant separation
- All tenants share the same database, but data is separated using a tenant_id column.
- Easier to scale and maintain.
- Requires strict application-level enforcement of data isolation.
Why Use Multi-Tenancy in FastAPI?
- Scalability: One codebase can handle multiple customers.
- Cost efficiency: Shared infrastructure reduces hosting costs.
- Maintainability: Deploy, monitor, and upgrade only one application instead of multiple.
Implementing Multi-Tenancy in FastAPI Let’s build an example where we implement a shared-database multi-tenancy approach in FastAPI using SQLAlchemy.
Step 1: Project Setup Install required dependencies:
pip install fastapi uvicorn sqlalchemy psycopg2-binary
Step 2: Database Model with Tenant Separation We’ll create a users table where each user is linked to a tenant via tenant_id.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class Tenant(Base):
__tablename__ = "tenants"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True)
users = relationship("User", back_populates="tenant")
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
tenant_id = Column(Integer, ForeignKey("tenants.id"))
tenant = relationship("Tenant", back_populates="users")
Here:
- Tenant table stores tenant information.
- User table is linked to tenants through tenant_id.
Step 3: Middleware for Tenant Identification We need to identify which tenant is making a request. One common approach is to use a custom header (X-Tenant-ID).
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.middleware("http")
async def tenant_middleware(request: Request, call_next):
tenant_id = request.headers.get("X-Tenant-ID")
if not tenant_id:
raise HTTPException(status_code=400, detail="X-Tenant-ID header missing")
request.state.tenant_id = tenant_id
response = await call_next(request)
return response
This middleware ensures that every request includes a tenant ID.
Step 4: Dependency for Tenant-Aware Queries We’ll use request.state.tenant_id to filter tenant-specific data.
from fastapi import Depends
def get_tenant_id(request: Request):
return request.state.tenant_id
Step 5: Tenant-Specific Endpoints
Now, let’s implement endpoints to create and fetch users for a specific tenant.
from fastapi import Depends
from sqlalchemy.orm import Session
from database import get_db # assume we have a DB session dependency
from models import User
@app.post("/users/")
def create_user(name: str, db: Session = Depends(get_db), tenant_id: str = Depends(get_tenant_id)):
user = User(name=name, tenant_id=tenant_id)
db.add(user)
db.commit()
db.refresh(user)
return user
@app.get("/users/")
def list_users(db: Session = Depends(get_db), tenant_id: str = Depends(get_tenant_id)):
users = db.query(User).filter(User.tenant_id == tenant_id).all()
return users
Here:
- create_user ensures that new users are always linked to the current tenant.
- list_users ensures that only tenant-specific users are retrieved.
Step 6: Testing the Multi-Tenancy
Create a Tenant
INSERT INTO tenants (name) VALUES ('Tenant A'), ('Tenant B');
Add Users for Tenant A
curl -X POST "http://localhost:8000/users/" -H "X-Tenant-ID: 1" -d "name=Alice" curl -X POST "http://localhost:8000/users/" -H "X-Tenant-ID: 1" -d "name=Bob"
Add Users for Tenant B
curl -X POST "http://localhost:8000/users/" -H "X-Tenant-ID: 2" -d "name=Charlie"
Fetch Users for Tenant A
curl -X GET "http://localhost:8000/users/" -H "X-Tenant-ID: 1"
- Returns: Alice, Bob
Fetch Users for Tenant B
curl -X GET "http://localhost:8000/users/" -H "X-Tenant-ID: 2"
- Returns: Charlie
Best Practices for Multi-Tenancy in FastAPI
- Always enforce tenant filtering in every database query.
- Use middleware to extract tenant identifiers from headers or subdomains.
- Consider database-per-tenant if you need strict data isolation.
- Secure tenant identification — don’t allow clients to manipulate tenant IDs directly; use authentication tokens linked to tenants.
- Scalability — monitor database size and query performance as tenants increase.
Conclusion Multi-tenancy is a powerful design pattern for SaaS applications. In this blog, we explored how to implement tenant-aware APIs in FastAPI using a shared-database approach. By enforcing tenant separation at the middleware and query level, we can efficiently serve multiple tenants while maintaining data security. For large-scale SaaS applications, carefully evaluate between database-per-tenant and shared-database multi-tenancy based on your isolation, cost, and scalability requirements.
Read the full article here: https://medium.com/@rameshkannanyt0078/multi-tenancy-in-fastapi-a-complete-guide-e6923702490a