Getting Started#

This guide walks from zero to a working REST API with FastAPI-Restly. Python 3.10 or later is required.

Installation#

Install the framework and an async driver for your database:

pip install "fastapi-restly[standard]" aiosqlite

The base package intentionally stays small. The standard extra adds FastAPI’s standard server dependencies (the fastapi dev toolchain), mirroring fastapi[standard]. Restly is database-driver-agnostic, so the async driver is a separate, explicit dependency: aiosqlite for the SQLite examples in this guide, or asyncpg/psycopg for PostgreSQL. Test tooling lives in its own extra; see Testing for fastapi-restly[testing].

Create an app#

restly new myapp generates a complete project; see Generate it. This guide builds one by hand instead, starting from a single file, main.py:

from contextlib import asynccontextmanager

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

fr.configure(async_database_url="sqlite+aiosqlite:///app.db")


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    email: Mapped[str]


@asynccontextmanager
async def lifespan(_app: FastAPI):
    # Dev/demo table creation only; use Alembic migrations in production.
    # Runs after model classes are declared, so metadata contains every table.
    await fr.db.async_create_all(Base)
    yield


app = FastAPI(lifespan=lifespan)


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

A few details are worth noting:

  • The model uses standard SQLAlchemy declarative style: your own DeclarativeBase, an explicit __tablename__, and an explicit primary-key column.

  • If you prefer dataclass-oriented SQLAlchemy models, FastAPI-Restly also provides fr.DataclassBase and fr.IDBase convenience bases. On those bases, Mapped[datetime] represents a UTC instant and maps to DateTime(timezone=True). PostgreSQL enforces that through its column type, while SQLite returns naive datetime values. Declare mapped_column(DateTime()) explicitly only for a field that intentionally stores a timezone-free wall-clock value.

  • RestView and AsyncRestView expect a single primary-key column; for composite-key tables, see the view hierarchy.

  • With no manual schema, FastAPI-Restly generates the view’s schema UserSchema from your model; see Auto-Generated Schemas. OpenAPI shows the classes derived from it: UserResponse, UserCreate, UserUpdate and UserListResponse; see Generated Class Names.

  • The lifespan hook creates tables with the async engine configured by fr.configure(). Use Alembic migrations in production.

Auto-generated schemas are a good fit for internal tools and backoffice APIs, early project scaffolding or prototypes, and straightforward models with minimal validation rules.

Sync or async?#

Default to AsyncRestView for new services. Use sync RestView for sync-only libraries or sync-first codebases. Mixed projects are fine; choose per view.

Run the app#

With main.py in place, start the development server:

fastapi dev main.py

The fastapi dev command comes from the standard extra; the dev server is not for production. Then open http://127.0.0.1:8000/docs or http://127.0.0.1:8000/openapi.json.

Use the CRUD routes#

UserView inherits five endpoint methods from AsyncRestView. Registering it with prefix = "/users" exposes these routes:

  • GET /users

  • POST /users

  • GET /users/{id}

  • PATCH /users/{id}

  • DELETE /users/{id}

These endpoints work as soon as the server starts. A POST creates a row:

POST /users
{"name": "Jane", "email": "jane@example.com"}

The response is 201 Created with the stored record:

{
  "id": 1,
  "name": "Jane",
  "email": "jane@example.com"
}

The database assigned the id, and GET /users now returns that row in the default data envelope:

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

Update semantics are PATCH (partial update). See Default CRUD Routes for the full contract. Filter lists with query parameters, for example GET /users?name=Jane. See Filter, Sort, and Paginate Lists.

Add an explicit schema (optional)#

Auto-generated schemas can be replaced at any point. Replace the UserView definition above with:

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


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

schema is the view’s schema. Responses follow it, without its WriteOnly fields. Restly derives the create and update schemas from it unless you override schema_create or schema_update. fr.IDSchema includes id as fr.ReadOnly: present in responses, excluded from create/update. Use fr.ReadOnly[T] for other response-only fields and fr.WriteOnly[T] for input-only fields such as passwords (see ReadOnly and WriteOnly).

Choose explicit schemas for public API contracts you want to keep stable, custom validation logic, field aliases and strict response shaping, or extra clarity for teams that prefer less implicit behavior; Auto-Generated vs Explicit Schemas compares the two.

Grow beyond one file#

Keep the one-file layout while the application is small. Once it holds several resources, give each one a package with its own models.py, schemas.py, and views.py, and register them all from a VIEWS tuple in main.py. Project structure covers the full layout, the import direction it depends on, and which modules to add only once they earn their place. Generating a project with restly new and moving this code into it is a reasonable way to make the move.

Test quickly#

A quick check with FastAPI’s regular TestClient confirms the API works:

from fastapi.testclient import TestClient
from main import app

with TestClient(app) as client:
    res = client.post("/users", json={"name": "Jane", "email": "jane@example.com"})
    assert res.status_code == 201

For test isolation (rolling back test data between tests), see the Testing guide.

Next steps#

Build a Blog API adds a related model, explicit schemas, filtering, and broader tests. Customize the Blog API then adds overrides, custom routes, authorization, and shared base classes.

Use these pages for individual topics: