Real-World Usage Patterns¶
This page shows common patterns for using fastapi-filters in production applications.
Pattern 1: Define a Filters Module¶
Create a filters/ package in your application with reusable FilterSet classes:
app/
filters/
__init__.py
common.py
users.py
articles.py
api/
endpoints/
users.py
articles.py
models/
user.py
article.py
filters/common.py¶
Define base filter sets with shared fields:
from datetime import datetime
from fastapi_filters import FilterField, FilterSet
class TimestampFilters(FilterSet):
created_at: FilterField[datetime]
updated_at: FilterField[datetime]
filters/users.py¶
Define resource-specific filter sets that inherit common fields:
from fastapi_filters import FilterField, FilterSet
from .common import TimestampFilters
class UserFilters(TimestampFilters):
name: FilterField[str]
email: FilterField[str]
age: FilterField[int]
is_active: FilterField[bool]
filters/articles.py¶
from fastapi_filters import FilterField, FilterOperator, FilterSet
from .common import TimestampFilters
class ArticleFilters(TimestampFilters):
title: FilterField[str]
status: FilterField[str] = FilterField(
operators=[FilterOperator.eq, FilterOperator.ne, FilterOperator.in_],
)
author_id: FilterField[int]
tags: FilterField[list[str]]
Pattern 2: Internal Fields for Multi-Tenant Apps¶
Use internal fields to enforce tenant isolation without exposing the filter to the API:
from fastapi_filters import FilterField, FilterSet
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
tenant_id: FilterField[int] = FilterField(internal=True)
@app.get("/users")
async def get_users(
filters: UserFilters = Depends(),
current_user: User = Depends(get_current_user),
):
# Inject tenant filter programmatically
tenant_filters = UserFilters.from_ops(
UserFilters.tenant_id == current_user.tenant_id,
)
# Merge with user-provided filters
stmt = apply_filters(select(User), filters)
stmt = apply_filters(stmt, tenant_filters)
return (await db.scalars(stmt)).all()
Pattern 3: Extract and Apply Filters Separately¶
Use extract() to split filters across different query stages:
class OrderFilters(FilterSet):
status: FilterField[str]
total: FilterField[float]
customer_name: FilterField[str]
created_at: FilterField[datetime]
@app.get("/orders")
async def get_orders(filters: OrderFilters = Depends()):
# Extract customer filters to apply on a joined table
customer_filters = filters.extract("customer_name")
# Main query with remaining filters
stmt = select(Order).join(Customer)
stmt = apply_filters(stmt, filters) # status, total, created_at
# Apply customer filters with remapping
stmt = apply_filters(
stmt,
customer_filters,
remapping={"customer_name": "name"},
additional={"name": Customer.name},
)
return (await db.scalars(stmt)).all()
Pattern 4: Sorting with Filters on Every Endpoint¶
Create a reusable pattern for endpoints that always need both filters and sorting:
from fastapi import Depends
from fastapi_filters import FilterField, FilterSet, SortingValues, create_sorting
from fastapi_filters.ext.sqlalchemy import apply_filters_and_sorting
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
is_active: FilterField[bool]
created_at: FilterField[datetime]
user_sorting = create_sorting(
"name", "age", "created_at",
default=["+created_at"],
)
@app.get("/users")
async def list_users(
db: AsyncSession = Depends(get_db),
filters: UserFilters = Depends(),
sorting: SortingValues = Depends(user_sorting),
):
stmt = apply_filters_and_sorting(select(User), filters, sorting)
return (await db.scalars(stmt)).all()
@app.get("/users/active")
async def list_active_users(
db: AsyncSession = Depends(get_db),
filters: UserFilters = Depends(),
sorting: SortingValues = Depends(user_sorting),
):
# Combine API filters with a hardcoded filter
extra = UserFilters.from_ops(UserFilters.is_active == True)
stmt = apply_filters_and_sorting(select(User), filters, sorting)
stmt = apply_filters(stmt, extra)
return (await db.scalars(stmt)).all()
Pattern 5: Computed Columns with Additional Namespace¶
Use additional to filter and sort on computed values:
from sqlalchemy import func
class UserFilters(FilterSet):
full_name: FilterField[str]
age: FilterField[int]
@app.get("/users")
async def get_users(
filters: UserFilters = Depends(),
sorting: SortingValues = Depends(create_sorting("full_name", "age")),
):
stmt = apply_filters_and_sorting(
select(User),
filters,
sorting,
additional={
"full_name": func.concat(User.first_name, " ", User.last_name),
},
)
...
Summary¶
| Pattern | When to Use |
|---|---|
| Filters module | Always -- organizes filter sets in one place |
| Internal fields | Multi-tenant apps, enforcing server-side constraints |
| Extract filters | Joined queries, applying filters at different stages |
| Sorting + filters | Standard list endpoints with pagination |
| Computed columns | Filtering/sorting on derived values, concatenated fields |