⚡ BetterCRUD
Stop writing CRUD boilerplate. Generate a complete, production-ready CRUD API from one decorator.
FastAPI CRUD routing library — class-based, fully async, fully testable.
Documentation: https://bigrivi.github.io/better_crud/
Source Code: https://github.com/bigrivi/better_crud
🎯 The Problem
Every FastAPI project needs the same endpoints — GET/POST /resource, GET/PUT/DELETE /resource/{id}. And every project writes them by hand, over and over. The result: thousands of lines of nearly identical code, with filtering, pagination, and permissions bolted on inconsistently.
BetterCRUD eliminates that entire category of boilerplate. Define your model, write one decorator, and get a complete CRUD API — with rich filtering, pagination, sorting, relationship queries, soft delete, ACL hooks, and lifecycle events — all generated for you, while you stay in full control.
✨ What You Get
from better_crud import crud
@crud(
pet_router,
dto={"create": PetCreate, "update": PetUpdate},
serialize={"base": PetPublic},
)
class PetController():
service: PetService = Depends(PetService)
This one decorator generates 8 routes with:
- 🚀 Rich filtering — 27 operators (
$eq$cont$in$between$any…), nested$and/$or, relationship queries - 📄 Pagination — offset pagination, plus
always/optional/disabledmodes - 🔗 Relationship queries & storage — joins, loads, many-to-many / one-to-many / many-to-one / one-to-one
- 🔐 ACL hooks —
get_feature/get_actionfor permission guards, row-scoping viaauth.filter - ♻️ Lifecycle hooks —
on_before/after_create/update/delete, plus soft delete with recover - 🧩 Custom endpoints —
@crud_actiondecorator for business actions likeadopt/approve - 🛠️ Extensible — custom backends, custom response schemas, custom pagination models
Requirements
- Python: Version 3.9 or newer.
- FastAPI: BetterCRUD is built to work with FastAPI, so having FastAPI in your project is essential.
- SQLAlchemy: Version 2.0.30 or newer. BetterCRUD uses SQLAlchemy for database operations.
- Pydantic: Version 2.7.3 or newer. BetterCRUD leverages Pydantic models for data validation and serialization.
Installation
pip install better-crud
Why BetterCRUD over fastapi-crudrouter?
The long-time de-facto CRUD library for FastAPI, fastapi-crudrouter, has been unmaintained since Nov 2023. BetterCRUD is actively maintained and provides a strict superset of its features:
| Feature | BetterCRUD | fastapi-crudrouter |
|---|---|---|
| Actively maintained (2026) | ✅ | ❌ (stalled since 2023) |
| FastAPI 0.141+ support | ✅ | ❌ |
| SQLAlchemy 2.0 async | ✅ | ✅ |
Rich filter operators ($eq $cont $notany $in …) |
✅ | ❌ |
| Nested relationship queries & joins | ✅ | ❌ |
| Many-to-many relationship storage | ✅ | ❌ |
Lifecycle hooks (on_after_create/update/...) |
✅ | ❌ |
| ACL / permission guards | ✅ | ❌ |
| Soft delete + recover | ✅ | ❌ |
Custom endpoints (@crud_action) |
✅ | ❌ |
| Pagination | ✅ | ✅ |
| Class views & function views | ✅ | ✅ |
| Test coverage | 99% | — |
💡 Migrating from fastapi-crudrouter? The route layout is nearly identical (
GET/POST /resource,GET/PUT/DELETE /resource/{id}), so switching is mostly a drop-in change:
# Before (fastapi-crudrouter)
from fastapi_crudrouter import SQLAlchemyCRUDRouter
router = SQLAlchemyCRUDRouter(schema=PetCreate, create_schema=PetCreate,
update_schema=PetUpdate, db_model=Pet,
db=get_session)
# After (better-crud)
from better_crud import crud
pet_router = APIRouter()
@crud(pet_router,
dto={"create": PetCreate, "update": PetUpdate},
serialize={"base": PetPublic})
class PetController():
service: PetService = Depends(PetService)
Same routes, same REST semantics — but you gain filtering, pagination modes, ACL, soft delete, and a service layer you can override with business logic.
Default Routes
| Route | Method | Description |
|---|---|---|
| /resource | GET | Get Many |
| /resource/{id} | GET | Get One |
| /resource | POST | Create One |
| /resource/bulk | POST | Create Many |
| /resource/{id} | PUT | Update One |
| /resource/{ids}/bulk | PUT | Update Many |
| /resource/{ids} | DELETE | Delete Many |
Minimal Example
Prerequisites: prepare your database. Only asynchronous mode is supported — use aiomysql or aiosqlite.
db.py
from sqlalchemy.orm import DeclarativeBase, declared_attr
from typing import AsyncGenerator
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
DATABASE_URL = "sqlite+aiosqlite:///crud.db"
class MappedBase(DeclarativeBase):
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()
class Base(MappedBase):
__abstract__ = True
engine = create_async_engine(
DATABASE_URL,
echo=False,
poolclass=NullPool
)
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
)
async def get_session() -> AsyncGenerator[AsyncSession, None]:
async with SessionLocal() as session:
yield session
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(MappedBase.metadata.create_all)
First Define Your Model And Schema
model.py
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base
class Pet(Base):
__tablename__ = "pet"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String(100))
description: Mapped[str] = mapped_column(String(100))
schema.py
from typing import Optional, List
from pydantic import BaseModel
class PetBase(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
class PetPublic(PetBase):
id: int
class PetCreate(PetBase):
pass
class PetUpdate(PetBase):
pass
Next we need to create a service:
service.py
from better_crud.service.sqlalchemy import SqlalchemyCrudService
from .model import Pet
class PetService(SqlalchemyCrudService[Pet]):
def __init__(self):
super().__init__(Pet)
Next we need to define the controller and decorate it with the crud decorator Sure the controller is just a normal class,The crud decorator gives it super powers controller.py
from fastapi import APIRouter, Depends
from better_crud import crud
from .schema import PetCreate, PetUpdate, PetPublic
from .service import PetService
pet_router = APIRouter()
@crud(
pet_router,
dto={
"create": PetCreate,
"update": PetUpdate
},
serialize={
"base": PetPublic,
}
)
class PetController():
service: PetService = Depends(PetService)
Next we can register router to the fastapi routing system
main.py
from better_crud import BetterCrudGlobalConfig
from fastapi import FastAPI
from contextlib import asynccontextmanager
from .db import get_session, init_db
BetterCrudGlobalConfig.init(
backend_config={
"sqlalchemy": {
"db_session": get_session
}
}
)
@asynccontextmanager
async def lifespan(_: FastAPI):
await init_db()
# Shutdown
yield
app = FastAPI(lifespan=lifespan)
def register_router():
from app.controller import pet_router
app.include_router(pet_router, prefix="/pet")
register_router()
Congratulations, your first CRUD route has been created! 🎉
Author
👤 bigrivi
- GitHub: bigrivi
🤝 Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Credits
This project draws inspiration from the following frameworks:
UseCases
BetterCRUD is used in production by:
- black-panther — a real-world FastAPI project
⭐️ Support
If BetterCRUD saves you time, please help more developers discover it:
- Give the repo a star ⭐️ — it directly helps others find the project
- Report bugs or request features via issues
- Share it in your team or community
License
Metadata
Release files for better-crud 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| better_crud-0.2.4.tar.gz | 28.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| better_crud-0.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.4 kB
Release files / better_crud-0.2.4.tar.gz
| Download URL | better_crud-0.2.4.tar.gz |
|---|---|
| Size | 28.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e60835b585e76fc6fb27034a85db3a3bec3c45b901df6010acb05d701c529454
|
|
BLAKE2b-256 checksum How to use checksums |
46c819e27231dee5b9327ea8e29d72240c1a61b510ec49f65b124aadfc3fd35e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / better_crud-0.2.4-py3-none-any.whl
| Download URL | better_crud-0.2.4-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e79d2c22352ae645798c83b95d407cb3ef43ab5bb78846389cb918e207dbc42
|
|
BLAKE2b-256 checksum How to use checksums |
bc462ec7608f4178c98ff070c9d90dc6a90c184439c5f4383f7cb1f023ca326e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency log