Auto-CRUD

Auto-CRUD is the fastest path to a production-ready REST API for a model. Two lines declare a complete CRUD surface — no boilerplate use cases needed.

Minimal example

from loom.core.model import ColumnField, TimestampedModel
from loom.rest.model import PaginationMode, RestInterface


class Product(TimestampedModel):
    __tablename__ = "products"

    id: int = ColumnField(primary_key=True, autoincrement=True)
    sku: str = ColumnField(length=64, unique=True, index=True)
    name: str = ColumnField(length=150)
    price_cents: int = ColumnField()
    stock: int = ColumnField()


class ProductInterface(RestInterface[Product]):
    prefix = "/products"
    tags = ("Products",)
    auto = True                                  # ← one flag, full CRUD
    pagination_mode = PaginationMode.CURSOR

That declaration generates five routes automatically:

Method

Path

Description

GET

/products

Paginated list (cursor or offset)

GET

/products/{id}

Fetch by primary key

POST

/products

Create new entity

PATCH

/products/{id}

Partial update

DELETE

/products/{id}

Delete by primary key

No use-case classes, no route declarations, no wiring code.

Restrict which routes are generated

Use include to expose only a subset:

class ProductInterface(RestInterface[Product]):
    prefix = "/products"
    tags = ("Products",)
    auto = True
    include = ("get", "list", "update")   # skips create and delete
    pagination_mode = PaginationMode.CURSOR

Valid values: "get", "list", "create", "update", "delete".

Multiple read profiles

Expose a profile query parameter so callers choose the loading depth:

class ProductInterface(RestInterface[Product]):
    prefix = "/products"
    tags = ("Products",)
    auto = True
    profile_default = "default"
    allowed_profiles = ("default", "with_details")
    expose_profile = True
    pagination_mode = PaginationMode.CURSOR

A ?profile=with_details query parameter is accepted on GET and list routes.

Mix auto-CRUD with custom routes

Use build_auto_routes() to inject auto-generated routes alongside explicit ones:

from loom.rest.autocrud import build_auto_routes
from loom.rest.model import PaginationMode, RestInterface, RestRoute

from app.product.model import Product
from app.product.use_cases import ListLowStockProductsUseCase, DispatchRestockEmailUseCase


class ProductInterface(RestInterface[Product]):
    prefix = "/products"
    tags = ("Products",)
    pagination_mode = PaginationMode.CURSOR
    routes = (
        RestRoute(
            use_case=ListLowStockProductsUseCase,
            method="GET",
            path="/low-stock",
            summary="List low stock products",
        ),
        RestRoute(
            use_case=DispatchRestockEmailUseCase,
            method="POST",
            path="/{product_id}/jobs/restock-email",
            summary="Dispatch restock email job",
            status_code=202,
        ),
        *build_auto_routes(Product, ()),   # ← full CRUD appended
    )

build_auto_routes(model, include) accepts an include tuple to restrict generated routes — pass () for all five.

Bootstrap

Wire the interface to FastAPI via the YAML config:

# config/api.yaml
app:
  name: my_store
  code_path: src
  discovery:
    mode: modules
    modules:
      include:
        - app.product.model
        - app.product.interface
  rest:
    backend: fastapi
    title: My Store API
    version: 0.1.0

database:
  url: ${oc.env:DATABASE_URL,sqlite+aiosqlite:///store.db}
# main.py
from loom.rest.fastapi.auto import create_app

app = create_app("config/api.yaml")

Three lines of application code. The framework handles DB wiring, DI, and route mounting.

When to add explicit use cases

Use auto-CRUD when the entity has no invariants beyond basic CRUD mechanics. Add explicit UseCase classes when you need:

  • Field validation (Rule) or derived fields (Compute)

  • Cross-entity checks (Exists, LoadById)

  • Job dispatch or workflow orchestration

  • Custom authorization or multi-step sequences

See Use-case DSL for patterns that compose on top of auto-CRUD.

Full working example

The companion repository dummy-loom shows auto-CRUD alongside custom use cases, background jobs, and Celery workers in a runnable PostgreSQL application. Start it with:

git clone https://github.com/the-reacher-data/dummy-loom
cd dummy-loom
make up      # starts postgres + redis + API + celery worker + flower