Using RestView#

AsyncRestView and RestView define five CRUD endpoint methods for one SQLAlchemy model. A subclass supplies the URL prefix, model, and optional Pydantic schemas. include_view() registers the inherited endpoint methods as FastAPI routes:

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User

UserView now serves list, create, retrieve, update, and delete requests under /users. Getting Started supplies the surrounding app, database configuration, and model in one runnable file.

Choose async or sync#

Use AsyncRestView for new applications unless the application already uses synchronous SQLAlchemy. It receives an AsyncSession through fr.AsyncSessionDep, and methods you override are normally async def methods.

Use RestView with synchronous SQLAlchemy sessions or sync-only libraries. It receives a Session through fr.SessionDep. Both classes expose the same routes, configuration, and override points. Mixed applications can choose per view.

Define the resource#

Every CRUD view needs two class attributes:

  • prefix is the URL prefix shared by every route in the view.

  • model is the SQLAlchemy mapped class that the routes read and write.

Ordinary models based on SQLAlchemy’s DeclarativeBase work. Restly’s IDBase is an optional convenience base, not a requirement.

If the view does not declare schema, Restly generates the view’s schema from model when the view is registered. It then derives the create and update schemas from it. Write the view’s schema yourself when the wire contract needs stable field names, validation, aliases, or fields that do not match the table directly:

class UserSchema(fr.IDSchema):
    name: str
    email: str


@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    schema = UserSchema

The four schema attributes have separate jobs:

Attribute

Used for

Default

schema

The source that Restly derives the other three from

Constructed from model when omitted

schema_response

GET responses and successful write responses

schema without WriteOnly fields

schema_create

POST request body

schema without ReadOnly fields

schema_update

PATCH request body

Writable fields from schema, made optional

Both derived schemas also leave out a primary key the server generates (autoincrement, a column default, or a dataclass field with init=False), marked ReadOnly or not. A natural key stays writable.

Custom Schemas and Field Types owns field markers, aliases, validation, and the choice between explicit and constructed schemas.

For larger applications, define the class without registration side effects and include it where the app or router is assembled:

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


fr.include_view(app, UserView)

Both registration forms produce the same routes. Project structure shows where the direct call belongs in a multi-package application.

Default CRUD behavior#

For prefix = "/users", include_view registers the inherited endpoint methods as this HTTP contract:

Request

Endpoint method

Input

Response

Status

GET /users

get_many_endpoint

List query parameters

Paginated data envelope

200

POST /users

create_endpoint

schema_create

schema

201

GET /users/{id}

get_one_endpoint

Scalar path ID

schema

200

PATCH /users/{id}

update_endpoint

schema_update

schema

200

DELETE /users/{id}

delete_endpoint

Scalar path ID

Empty body

204

The collection path without a trailing slash is canonical. Restly also accepts the trailing-slash form as a hidden compatibility route.

List filters are derived from fields shared by schema and model. Sorting uses ?sort=field,-other_field. Pagination is on by default with a page size of 50, a maximum accepted page size of 1000, and a response shaped like:

{
  "data": [{"id": 1, "name": "Jane", "email": "jane@example.com"}],
  "total_count": 1,
  "page": 1,
  "page_size": 50,
  "total_pages": 1
}

Unknown list query keys return 422 instead of being ignored. Filter, Sort, and Paginate Lists defines the complete query grammar. Response Envelopes and List Metadata defines the default containers and how to replace them.

Missing or hidden rows return 404. On the default endpoints, the create, update, and delete handlers run authorization, call the business method, run the commit hooks, and commit before the response is built. A custom endpoint can group several handlers under one shared_write_action_commit() block. The outermost block then owns the commit and after-hooks. Reads do not commit.

Important

Restly does not authenticate requests or impose an authorization policy. authorize() permits every action unless a subclass overrides it. Add authentication through FastAPI dependencies, then enforce per-action policy in authorize() where needed.

Item paths and static routes#

The built-in GET, PATCH, and DELETE item paths follow id_type. By default, it is inferred from the model’s primary key. React Admin’s PUT uses the same path.

ID type

Registered item path

Matching values

int

/{id:int}

Non-negative digits, including 0

uuid.UUID

/{id:uuid}

UUIDs accepted by Starlette’s converter

Any other type

/{id}

Any single path segment, then FastAPI validation

An integer or UUID item route leaves /users/me available to a static route registered after the view. A value that does not match the converter returns 404 instead of the previous 422, unless another route handles it. Negative integer IDs do not match, even if the row exists. OpenAPI still shows /users/{id} with the integer or UUID parameter schema.

Custom endpoint declarations keep their paths. To accept negative integer IDs, replace each needed item endpoint with an untyped path such as @fr.get("/{id}") and annotate its id parameter as int. See Customizing RestView for endpoint replacements. Register static routes before untyped item routes, including views with string IDs.

Configure a view#

Configure the class according to the contract the resource needs:

Requirement

Configuration

Stable response fields

Declare schema

Different create or update validation

Declare schema_create or schema_update

A different type for the {id} path parameter

Set id_type

Read-only or otherwise restricted routes

Set exclude_routes with fr.ViewRoute values

Disable pagination

Set pagination to None, or to a NoPagination to choose its envelope

Change page limits, parameter names, or the list envelope

Set pagination to a NumberedPagination

Accept a view-specific list query key

Declare it on the endpoint method or in a dependency, or add it to extra_query_params when code reads it from the request

Apply FastAPI metadata or dependencies to every route

Set tags, responses, or dependencies

Set a route name, summary, or operation ID

Set route_options as shown in OpenAPI customization

Use another session dependency

Override the session annotation with Annotated[..., Depends(...)]

Behavior and Configuration Reference lists the exact attribute types and method signatures. Use Restly in an Existing Project covers custom engines, session generators, and a per-view session dependency.

Change behavior#

A request passes through an endpoint method, a handler, and a business method. Change the layer that owns the behavior instead of rewriting the entire route:

Change

Use

Stamp or transform data during create or update

Override the business method in Customizing RestView; a server-stamped field is a column default on the model

Hide rows from every read

Declare a scope: default_scope on the model, or scope on the view

Permit or reject an action

Override authorize()

Run an atomic side effect or a post-commit action

Override before_action_commit() or after_action_commit()

Commit several handlers or custom actions together

Wrap them in shared_write_action_commit()

Change status, headers, request parameters, or response model

Replace the endpoint method

Add a path that is not part of CRUD

Add a method with @fr.get, @fr.post, or another route decorator

Share behavior across resources

Use a base view or mixins

Replace the list query grammar

Follow Filter, Sort, and Paginate Lists

Customizing RestView owns the lifecycle diagrams, complete override decision table, and worked recipes.

Limits and alternatives#

The default CRUD contract has these boundaries:

  • Resource identity is one scalar primary key on the generated routes. The {id} path parameter takes the key’s Python type, so a UUID or string key needs no configuration. A composite key is reached from a custom route that loads with a predicate (Look a row up by another key), or from View with explicit route paths.

  • Nested response schemas and relationship filtering are supported. General nested create and update payloads are not. Use MustExist, IDRef, or IDSchema for model-aware references, or transform the payload in a business method. See Work with Foreign Keys and Relationships.

  • The default update route uses PATCH, not PUT. Add an explicit PUT route only when the client contract requires one.

  • A View groups non-CRUD endpoints without adding CRUD methods. A plain FastAPI route remains the clearest choice for a single endpoint that shares no view configuration.

Next steps#