Skip to main content

⚡ 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.

Tests PyPi Version Supported Python Versions


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 / disabled modes
  • 🔗 Relationship queries & storage — joins, loads, many-to-many / one-to-many / many-to-one / one-to-one
  • 🔐 ACL hooks — get_feature / get_action for permission guards, row-scoping via auth.filter
  • ♻️ Lifecycle hooks — on_before/after_create/update/delete, plus soft delete with recover
  • 🧩 Custom endpoints — @crud_action decorator for business actions like adopt / 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! 🎉

OpenAPI Route Overview

Author

👤 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".

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Credits

This project draws inspiration from the following frameworks:

UseCases

BetterCRUD is used in production by:

⭐️ 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

MIT

Metadata

Release files for better-crud 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for better-crud 0.2.3
File Size Uploaded
better_crud-0.2.3.tar.gz 28.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for better-crud 0.2.3
File Interpreter ABI Platform
better_crud-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 57.3 kB

Release files / better_crud-0.2.3.tar.gz

Download URL better_crud-0.2.3.tar.gz
Size 28.0 kB
Tags Source
SHA-256 checksum
How to use checksums
46510d9257900229e11f90116b12095fa9f24d3980a1da1802a1034ad05c66ce
BLAKE2b-256 checksum
How to use checksums
783bd836ed5fb91f30652599943b81f2db554a45fdf64ee118ae9821c0500ac2
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 17, 2026.

Transparency log

Release files / better_crud-0.2.3-py3-none-any.whl

Download URL better_crud-0.2.3-py3-none-any.whl
Size 29.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2f058816cc0d30e4b52c7c05b9ab1f43a5f422fb38c9c625ca445cb3f043c852
BLAKE2b-256 checksum
How to use checksums
34d2395f4385aa112f5d70014a5ab5baf61a6af940e6c23006efa97f54077108
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 17, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page