fastapi-crudrouter is Dead. Here's How to Migrate to BetterCRUD
fastapi-crudrouter was the de-facto CRUD library for FastAPI — generating 6 CRUD routes from a model in a few lines. It's been unmaintained since November 2023. No FastAPI 0.141+ support, no new features, and its SQLAlchemy backend doesn't keep up with SQLAlchemy 2.0 async best practices.
If you're still on it, it's time to migrate. The good news: the route layout is nearly identical, so the move is mostly drop-in.
Why migrate?
| fastapi-crudrouter | BetterCRUD | |
|---|---|---|
| Maintained (2026) | ❌ stalled since Nov 2023 | ✅ active |
| FastAPI 0.141+ | ❌ | ✅ |
| SQLAlchemy 2.0 async | ✅ (basic) | ✅ |
| Filter operators | ❌ | ✅ 27 operators |
| Pagination modes | ❌ basic only | ✅ always/optional/disabled |
| Relationship queries & storage | ❌ | ✅ joins, M2M, O2M, O2O |
| Soft delete + recover | ❌ | ✅ |
| ACL hooks | ❌ | ✅ |
| Lifecycle hooks | ❌ | ✅ |
| Custom endpoints | ❌ | ✅ @crud_action
|
| Test coverage | — | ✅ 99%+, 177 tests |
The migration
Before (fastapi-crudrouter)
from fastapi_crudrouter import SQLAlchemyCRUDRouter
from database import get_session
from models import Pet, PetCreate, PetUpdate
router = SQLAlchemyCRUDRouter(
schema=PetUpdate,
create_schema=PetCreate,
update_schema=PetUpdate,
db_model=Pet,
db=get_session,
prefix="pet",
)
After (BetterCRUD)
from fastapi import APIRouter, Depends
from better_crud import crud
from better_crud.service.sqlalchemy import SqlalchemyCrudService
from models import Pet, PetCreate, PetUpdate, PetPublic
pet_router = APIRouter()
class PetService(SqlalchemyCrudService[Pet]):
def __init__(self):
super().__init__(Pet)
@crud(
pet_router,
dto={"create": PetCreate, "update": PetUpdate},
serialize={"base": PetPublic},
)
class PetController():
service: PetService = Depends(PetService)
That's the whole migration. Register pet_router with app.include_router(pet_router, prefix="/pet") and you're live.
What you get for free after migrating
1. Filtering you actually need
fastapi-crudrouter had no filtering. BetterCRUD's GET /pet supports 27 operators out of the box:
GET /pet?filter=name||$cont||Re # contains
GET /pet?filter=age||$between||1,5 # range
GET /pet?filter=species||$in||dog,cat # in list
GET /pet?s={"$or":[{"age":{"$gt":3}},{"species":{"$eq":"cat"}}]} # nested logic
2. Pagination that fits your API
BetterCrudGlobalConfig.init(pagination_mode="optional")
Three modes — always, optional, disabled — so small reference datasets can return plain arrays while large tables stay paginated.
3. Relationships, done properly
Store nested payloads (many-to-many, one-to-many, one-to-one) automatically, and query them with ?load= / ?join=:
class UserCreate(UserBase):
profile: Optional[UserProfileCreate] = None
roles: Optional[List[int]] = None
tasks: Optional[List[UserTaskCreate]] = None
4. Soft delete + recover
@crud(router, query={"soft_delete": True, "allow_recover": True})
DELETE becomes soft delete; PATCH /pet/{id}/recover restores records.
5. Security hooks
Every generated route exposes feature/action on the request state — wire your existing permission guards directly:
from better_crud import get_feature, get_action
async def acl(request: Request):
feature = get_feature(request)
action = get_action(request)
# your ACL logic
6. Business logic without leaving the CRUD pattern
@crud_action(method="POST", path="/{id}/adopt", action="adopt")
async def adopt(self, id: int):
return await self.service.do_adopt(id)
Registers POST /pet/{id}/adopt with service injection, ACL, and response wrapping — no manual router wiring.
Migration checklist
- ✅ Add
better_crudto requirements (removefastapi-crudrouter) - ✅ Add a
PetService(SqlalchemyCrudService[Pet])class (thin, no logic needed initially) - ✅ Replace the
SQLAlchemyCRUDRouter(...)call with@crud(...)on a controller class - ✅ Register the router — prefix stays the same, routes are identical
- ✅ Point your frontend at the same endpoints — no client changes needed
- ✅ Optionally enable soft delete, ACL, and custom actions as you go
The route layout (GET/POST /resource, GET/PUT/DELETE /resource/{id}) is preserved, so your existing clients keep working while the backend gains capabilities fastapi-crudrouter never had.
Resources
- Docs: https://bigrivi.github.io/better_crud/
- Source: https://github.com/bigrivi/better_crud
- Install:
pip install better-crud
Migrating? If BetterCRUD saves you time, give it a ⭐ on GitHub.
Top comments (0)