Skip to content

Models

Models represent database tables and are the primary interface for reading and writing data. Each model maps to a single table and exposes an async query builder.

Defining a Model

Extend Model from fastapi_startkit.masoniteorm and annotate your columns as class-level type hints:

python
from fastapi_startkit.masoniteorm import Model

class User(Model):
    __table__ = "users"

    id: int
    name: str
    email: str

__table__

By default the ORM infers the table name from the class name (pluralized, snake_cased). Set __table__ explicitly to override:

python
class PostTag(Model):
    __table__ = "post_tag"

__timestamps__

Timestamp columns (created_at, updated_at) are managed automatically. Set __timestamps__ = False to disable them:

python
class PostTag(Model):
    __table__ = "post_tag"
    __timestamps__ = False

Querying

All query methods are async and must be awaited.

Fetch all records

python
users = await User.all()

Fetch the first record

python
user = await User.first()

Find by primary key

python
user = await User.find(1)

Filter with where

python
user = await User.where("email", "admin@example.com").first()

get() — execute a query

python
posts = await Post.where("user_id", 1).get()

find_or_fail(id) — find or raise

Fetch a record by primary key, raising ModelNotFoundException if nothing is found:

python
user = await User.find_or_fail(1)

first_or_fail() — first or raise

Execute the current query and raise ModelNotFoundException if no record matches:

python
user = await User.where("email", "admin@example.com").first_or_fail()

exists() — check existence

Return True if any record matches the current query, False otherwise:

python
active = await User.where("is_active", True).exists()

count() — row count

Return the number of rows matching the current query:

python
total = await User.count()
active_count = await User.where("is_active", True).count()

paginate(per_page, page) — paginate results

Return a LengthAwarePaginator containing the requested slice of records plus total-count metadata:

python
page = await User.paginate(per_page=15, page=1)
# page.items      → list of User instances
# page.total      → total number of matching rows
# page.last_page  → last available page number

or_where / where_raw / or_where_raw / or_where_null — raw & OR variants

python
# OR condition
users = await User.where("role", "admin").or_where("role", "moderator").get()

# Raw SQL predicate
users = await User.where_raw("created_at > NOW() - INTERVAL '7 days'").get()

# Raw SQL with OR
users = await User.where("is_active", True).or_where_raw("role = 'superadmin'").get()

# OR IS NULL
users = await User.where("name", "Alice").or_where_null("deleted_at").get()

Creating Records

create

Insert a new record and return the model instance:

python
post = await Post.create(
    user_id=user.id,
    title="Hello World",
    content="My first post."
)

first_or_create

Fetch a matching record or create it if it does not exist. The first dict is the lookup condition, the second is the data to merge on create:

python
user = await User.first_or_create(
    {"email": "admin@example.com"},
    {"name": "Admin User", "password": "secret"}
)

insert — bulk insert rows

Insert one or many rows without instantiating individual model instances:

python
await User.insert({"email": "picard@example.com", "votes": 0})

await User.insert([
    {"email": "picard@example.com", "votes": 0},
    {"email": "janeway@example.com", "votes": 0},
])

Unlike create, insert does not fire model events, apply timestamps, or return model instances. It is intended for high-volume seed and batch operations.

Updating Records

python
user = await User.find(1)
user.name = "Jane Doe"
await user.save()

Deleting Records

python
user = await User.find(1)
await user.delete()

Accessing Attributes

Model attributes map directly to column names:

python
user = await User.first()
print(user.id)
print(user.name)
print(user.email)
print(user.created_at.diff_for_humans())  # pendulum datetime

Complete Example

The Post model from the example app shows columns and relationships together:

python
# app/models/post.py
from typing import TYPE_CHECKING
from fastapi_startkit.masoniteorm import Model
from fastapi_startkit.masoniteorm.relationships import BelongsTo, HasMany, BelongsToMany

if TYPE_CHECKING:
    from app.models.user import User
    from app.models.tag import Tag
    from app.models.media import Media

class Post(Model):
    __table__ = "posts"

    id: int
    user_id: int
    title: str
    content: str

    author: "User" = BelongsTo('User', local_key='user_id', foreign_key="id")
    media: list["Media"] = HasMany("Media")
    tags: list["Tag"] = BelongsToMany("Tag", "post_id", "tag_id", table="post_tag")

Relationship declarations are covered in the Relationships section.

Automatic type coercion and custom value objects are covered in the Casts section.