How to Add Mixins Like UUIDMixin, TimestampMixin, and SoftDeleteMixin to FastAPI Models

In the benavlabs/fastapi-boilerplate repository, you add mixins to SQLAlchemy models by importing UUIDMixin, TimestampMixin, and SoftDeleteMixin from src/app/core/db/models.py and including them in your class inheritance list alongside the declarative Base.

The benavlabs/fastapi-boilerplate project implements modern SQLAlchemy 2.0 dataclass-style declarative models that inherit from a common Base defined in src/app/core/db/database.py (lines 10-14). Instead of repeating column definitions for UUID primary keys, automatic timestamps, and soft-delete flags across every model, the boilerplate provides reusable mixins that inject these columns automatically.

What the Built-in Mixins Provide

The mixin module at src/app/core/db/models.py (lines 10-28) contains three declarative mixins designed for common audit and identification patterns.

UUIDMixin

UUIDMixin adds a uuid column that serves as a unique identifier using the UUIDv7 standard. According to the source code at lines 10-13, it generates the UUID via uuid7() in Python while setting a server-side default of gen_random_uuid() for database inserts.


# From src/app/core/db/models.py

class UUIDMixin:
    uuid: Mapped[uuid_pkg.UUID] = mapped_column(
        UUID(as_uuid=True), 
        unique=True, 
        default_factory=uuid7, 
        server_default=text("gen_random_uuid()")
    )

TimestampMixin

TimestampMixin automates created_at and updated_at tracking. As implemented at lines 16-22, it uses datetime.now(UTC) for Python-side defaults and current_timestamp(0) for the database defaults, ensuring updated_at refreshes on every modification.


# From src/app/core/db/models.py

class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), 
        default_factory=lambda: datetime.now(UTC), 
        server_default=text("current_timestamp(0)")
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), 
        default_factory=lambda: datetime.now(UTC), 
        server_default=text("current_timestamp(0)"), 
        onupdate=lambda: datetime.now(UTC)
    )

SoftDeleteMixin

SoftDeleteMixin enables logical deletion without removing records. Lines 25-27 define a nullable deleted_at timestamp and an is_deleted boolean flag defaulted to False, allowing you to filter out deleted rows while preserving historical data.


# From src/app/core/db/models.py

class SoftDeleteMixin:
    deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None)
    is_deleted: Mapped[bool] = mapped_column(default=False, index=True)

How to Apply Mixins to Your Models

Follow these steps to refactor existing models or create new ones using the provided mixins.

  1. Import the mixins from the core module:

    from ..core.db.models import UUIDMixin, TimestampMixin, SoftDeleteMixin
  2. Add the mixins to your class inheritance after Base. The order does not affect SQLAlchemy behavior, but conventionally place Base first followed by the mixins.

  3. Remove duplicate column definitions that the mixins now provide. Delete manual declarations of uuid, created_at, updated_at, deleted_at, and is_deleted.

  4. Retain model-specific columns such as name, email, or foreign keys.

Practical Refactoring Examples

Refactoring the User Model

The original User model in src/app/models/user.py (lines 12-30) manually declares all audit columns. Here is the refactored version using all three mixins:


# src/app/models/user.py

import uuid as uuid_pkg
from datetime import UTC, datetime
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column

from ..core.db.database import Base
from ..core.db.models import UUIDMixin, TimestampMixin, SoftDeleteMixin


class User(Base, UUIDMixin, TimestampMixin, SoftDeleteMixin):
    __tablename__ = "user"

    # Keep the integer primary key if your application requires it alongside UUID

    id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True, init=False)
    
    name: Mapped[str] = mapped_column(String(30))
    username: Mapped[str] = mapped_column(String(20), unique=True, index=True)
    email: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    hashed_password: Mapped[str] = mapped_column(String)
    
    profile_image_url: Mapped[str] = mapped_column(
        String, default="https://profileimageurl.com"
    )
    tier_id: Mapped[int | None] = mapped_column(
        ForeignKey("tier.id"), index=True, default=None, init=False
    )

Refactoring the Tier Model

For the Tier model in src/app/models/tier.py (lines 9-16), which only requires timestamp tracking, import only TimestampMixin:


# src/app/models/tier.py

from datetime import UTC, datetime
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

from ..core.db.database import Base
from ..core.db.models import TimestampMixin


class Tier(Base, TimestampMixin):
    __tablename__ = "tier"

    id: Mapped[int] = mapped_column(
        autoincrement=True,
        nullable=False,
        unique=True,
        primary_key=True,
        init=False,
    )
    name: Mapped[str] = mapped_column(String, nullable=False, unique=True)

Benefits of Using SQLAlchemy Mixins

  • DRY Principle: Column definitions exist in a single location. If you need to switch from uuid7() to uuid4() or adjust timezone handling, you change it once in src/app/core/db/models.py rather than across every model file.
  • Schema Consistency: Every model using TimestampMixin has identical column names and database defaults, simplifying query logic and migration scripts.
  • Soft-Delete Ready: SoftDeleteMixin provides the infrastructure for logical deletion immediately. You can filter queries with is_deleted=False without altering the database schema later.

Summary

  • Import UUIDMixin, TimestampMixin, and SoftDeleteMixin from src/app/core/db/models.py to add reusable columns to your SQLAlchemy models.
  • Inherit these mixins alongside the declarative Base class defined in src/app/core/db/database.py.
  • Remove redundant column definitions from your model classes after adding the mixins.
  • The CRUD utilities in the boilerplate automatically recognize these mixin columns, requiring no changes to your data access layer.

Frequently Asked Questions

What order should I inherit the mixins in my model class?

You should place the declarative Base first, followed by the mixins in any order (e.g., class User(Base, UUIDMixin, TimestampMixin, SoftDeleteMixin)). SQLAlchemy's declarative system resolves column definitions from left to right, but since these mixins define non-overlapping columns, the inheritance order does not affect the final schema.

Can I use only one mixin, or do I need to include all three?

You can import and use any combination of mixins independently. For example, reference models might only need TimestampMixin, while entity models might use all three. Simply import only the mixins your specific model requires from src/app/core/db/models.py.

Do I need to update CRUD operations when using SoftDeleteMixin?

The existing CRUD utilities in src/app/crud/crud_users.py and similar files automatically recognize the is_deleted and deleted_at columns. You can implement soft-delete filtering by adding is_deleted=False to your SQLAlchemy queries without modifying the CRUD base classes, as the columns are standard mapped attributes on the model.

Where is the declarative Base defined that I must inherit alongside the mixins?

The declarative Base is defined in src/app/core/db/database.py at lines 10-14. All models in the benavlabs/fastapi-boilerplate must inherit from this Base class to ensure proper registry and metadata configuration, regardless of whether they use the provided mixins.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →