Custom Endpoints
Mixins automatically register standard CRUD routes. When you need an endpoint that doesn't fit the standard pattern, you can add it manually — both on the backend and the frontend.
Backend (Python / FastAPI)
How mixins register routes
Each mixin declares a class-level __router (APIRouter) and decorates its methods with standard FastAPI route decorators:
python
class ListMixin(Generic[T], ABC):
__router = APIRouter()
@final
@__router.get("")
async def list_items(self: ..., context: Context) -> list[T]:
return await self.perform_list(context)route_viewset collects all __router instances from the MRO and registers their routes on the application router.
Adding a custom route
Declare your own __router on the ViewSet class and decorate a method with it. route_viewset will pick it up automatically alongside the mixin routes:
python
from fastapi import APIRouter
from fastapi_viewsets.collection_viewset import CollectionViewSet
from fastapi_viewsets.context import Context
from fastapi_viewsets.decorators.route_viewset import route_viewset
from fastapi_viewsets.mixins import BulkViewSetMixin, LookupItem, LookupMixin
router = APIRouter()
@route_viewset(router, base_path="/items", pk_field_name="id")
class ItemViewSet(CollectionViewSet[int, Item], BulkViewSetMixin[int, Item], LookupMixin):
__router = APIRouter() # custom router for this class
def __init__(self):
super().__init__(container=database, pk_field="id")
async def perform_lookup(self, context: Context) -> list[LookupItem]:
return [LookupItem(group=None, pk=item.id, title=item.name, icon=None)
for item in await self.perform_list(context)]
@__router.get("search", tags=["Item"])
async def search(self, context: Context, q: str) -> list[Item]:
"""Return items whose name contains the query string."""
all_items = await self.perform_list(context)
return [item for item in all_items if q.lower() in item.name.lower()]This registers GET /items/search?q=... in addition to all standard mixin routes.
Custom POST endpoint with a request body
python
from pydantic import BaseModel
class CloneRequest(BaseModel):
source_id: int
new_name: str
@route_viewset(router, base_path="/items", pk_field_name="id")
class ItemViewSet(CollectionViewSet[int, Item], BulkViewSetMixin[int, Item]):
__router = APIRouter()
def __init__(self):
super().__init__(container=database, pk_field="id")
@__router.post("clone", tags=["Item"])
async def clone(self, context: Context, body: CloneRequest) -> Item:
"""Clone an existing item under a new name."""
source = await self.perform_retrieve(context, body.source_id)
return await self.perform_create(
context, Item(id=0, name=body.new_name, description=source.description)
)Notes
- The
__routerattribute on the ViewSet class must not shadow the one inherited from a mixin. Declare it as a freshAPIRouter()directly on your class. - Route paths are relative to
base_path."search"becomes/items/search. - All standard FastAPI route parameters (
tags,summary,response_model,status_code, …) are supported. - The method receives
self— the viewset instance — so you have full access toperform_*helpers and any other instance state.
