How-To: Use FastAPI-Restly in an Existing Project#

FastAPI-Restly registers ordinary FastAPI path operations. You can mount Restly views on the same FastAPI app or APIRouter as your existing routes, adopt it one resource at a time, and move individual endpoints back to plain FastAPI when that is the clearer shape.

Add Restly Next to Existing Routes#

Use fr.include_view(...) wherever you already compose routes. Existing routers and Restly views can share the same parent app or router:

from fastapi import APIRouter, FastAPI
import fastapi_restly as fr

from .orders import router as orders_router

app = FastAPI()
api = APIRouter(prefix="/api")

api.include_router(orders_router, prefix="/orders")  # existing FastAPI routes


class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    schema = UserRead


fr.include_view(api, UserView)  # generated /api/users routes

app.include_router(api)

Adoption is per resource. In the example above, orders stay hand-written while users use Restly. Adding a ProductView later does not require changing the orders router.

You can also keep plain FastAPI routes beside a Restly resource for endpoints that are not part of the CRUD surface:

@api.get("/users/{id}/export")
async def export_user(id: int):
    ...

Step Out for One Endpoint#

If one generated endpoint should be hand-written, exclude only that route and add the FastAPI route yourself:

class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    schema = UserRead
    exclude_routes = (fr.ViewRoute.DELETE,)


fr.include_view(api, UserView)


@api.delete("/users/{id}", status_code=204)
async def delete_user(id: int):
    ...

That leaves Restly responsible for list, create, read, and update while DELETE uses your ordinary FastAPI implementation. For smaller changes that keep the same HTTP contract, prefer overriding a perform_* handler; for a different status code, response shape, or query interface, see Override CRUD Behavior and Add Custom Endpoints.

Step Out for a Whole Resource#

There is no global Restly router to unwind. A resource is included only where you call fr.include_view(...). To move a resource back to plain FastAPI, remove that include call and register an APIRouter with the same prefix and path operations.

Your models, Pydantic schemas, dependencies, and database session wiring can stay in ordinary application modules. Restly does not need to own the whole app.

Provide Your Own Session Generator#

If your project already manages its own database sessions, configure FastAPI-Restly to use them instead of its built-in session factory.

If you provide custom sessionmakers or generators, make sure their lifecycle and session options match the behavior your views rely on. Restly’s built-in factories intentionally use different autoflush defaults for sync and async sessions and keep expire_on_commit=False for both; see Session Factory Defaults. Custom generators also own transaction handling: Restly does not add its commit_session_on_response behavior around them.

For async views (AsyncRestView), pass an async generator to fr.configure():

from typing import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession
import fastapi_restly as fr

async def my_get_db() -> AsyncIterator[AsyncSession]:
    ...
    yield MyAsyncSession()

fr.configure(session_generator=my_get_db)

For sync views (RestView), pass a sync generator:

from typing import Iterator
from sqlalchemy.orm import Session
import fastapi_restly as fr

def my_get_db() -> Iterator[Session]:
    ...
    yield MySession()

fr.configure(sync_session_generator=my_get_db)

Use a Custom Session Dependency on One View#

Use fr.configure(...) when one session source should be the default for the application. If only one view should use a different session source, override the view’s session dependency instead.

from collections.abc import AsyncIterator
from typing import Annotated

from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

import fastapi_restly as fr


reporting_engine = create_async_engine("postgresql+asyncpg://user:pass@reports/db")
ReportingSession = async_sessionmaker(
    bind=reporting_engine,
    autoflush=False,
    expire_on_commit=False,
)


async def get_reporting_db() -> AsyncIterator[AsyncSession]:
    async with ReportingSession() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise


ReportingSessionDep = Annotated[AsyncSession, Depends(get_reporting_db)]


@fr.include_view(app)
class ReportView(fr.AsyncRestView):
    prefix = "/reports"
    model = Report
    schema = ReportRead
    session: ReportingSessionDep

The custom dependency owns its own lifecycle, including commit/rollback policy. This is the recommended escape hatch for read replicas, reporting databases, or other per-view session wiring. Restly does not currently provide named engines or named Restly contexts.

You can also use FastAPI-Restly’s configured session proxy directly in your own code (for example in background tasks):

import fastapi_restly as fr

async with fr.open_async_session() as session:
    result = await session.execute(...)

Use Your Own DeclarativeBase Models#

If your project already has SQLAlchemy models on a custom DeclarativeBase, you can use those models directly in FastAPI-Restly views:

import fastapi_restly as fr
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class AppBase(DeclarativeBase):
    pass


class World(AppBase):
    __tablename__ = "world"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    message: Mapped[str]


@fr.include_view(app)
class WorldView(fr.AsyncRestView):
    prefix = "/world"
    model = World

FastAPI-Restly supports these models for generated CRUD routes and auto-generated schemas. When creating tables, use your own base metadata (for example AppBase.metadata.create_all(...)).