Fastro FastAPI Boilerplate

repository·main·Indexed 24 days ago

https://github.com/benavlabs/fastapi-boilerplate

A batteries-included, open-source FastAPI boilerplate for production-ready backends. It features a vertical-slice architecture with built-in authentication, CRUD, background jobs via Taskiq, caching (Redis/Memcached), and rate-limiting. Includes the `bp` CLI for deployment scaffolding, environment auditing, and plugin management, alongside support for SQLAlchemy 2.0, Pydantic v2, and Alembic migrations.

Tokens
101.9K
Snippets
235
Records
397
Agent score
83%

What's inside Fastro

  1. Overview of Fastro features

    main

    Fastro is a production-ready FastAPI boilerplate designed with vertical-slice modules and swappable infrastructure. Key features include:

    • Core: Fully async FastAPI, SQLAlchemy 2.0, and Pydantic v2.
    • Auth: Server-side sessions, CSRF, OAuth (Google), and API keys via crudauth.
    • Database & CRUD: PostgreSQL with Alembic migrations and FastCRUD for efficient generics and pagination.
    • Background Tasks: Taskiq workers using Redis or RabbitMQ.
    • Caching: Decorator-based caching (@cache) supporting Redis or Memcached.
    • Rate Limiting: Per-tier and per-path rules.
    • Admin: Optional SQLAdmin-based admin panel (toggled via environment variables).
    • CLI: The bp tool for deployment scaffolding, environment auditing, and plugin management.
  2. Core Technologies in Fastro

    main

    Fastro is built on a modern, async-first Python stack designed for high performance and scalability:

    • FastAPI: Web framework for building APIs.
    • Pydantic V2: High-speed data validation (Rust-based).
    • SQLAlchemy 2.0: SQL toolkit and ORM.
    • PostgreSQL: Relational database.
    • Redis: In-memory store for caching and message brokering.
    • Taskiq: Async-first task queue.
    • Docker: Containerization for deployment.
  3. Navigate the FastAPI Boilerplate User Guide

    main

    The User Guide is organized into several functional domains to help you build, secure, and scale your application. Depending on your goal, you should follow one of these primary tracks:

    Core Development

    • Project Structure & Configuration: Understand codebase organization and environment settings.
    • Database Operations: Work with SQLAlchemy models, Pydantic schemas, CRUD operations, and Alembic migrations.
    • API Development: Build endpoints, implement pagination, handle exceptions, and manage API versioning.

    Security & Administration

    • Security & Authentication: Implement session-based auth (HTTP-only cookies/CSRF), OAuth, API keys, user management, and role-based permissions.
    • Admin Panel: Use the SQLAdmin-powered interface to manage your database models and admin users.

    Scaling & Performance

    • Performance & Caching: Implement Redis-based server-side caching, HTTP client caching, and advanced invalidation strategies.
    • Background Processing: Use Taskiq (with Redis or RabbitMQ) for long-running asynchronous operations.
    • Rate Limiting: Protect your API using Redis-based rate limiting.
  4. Compare Fastro and FastroAI

    main

    Choose between the open-source foundation (Fastro) or the complete SaaS template (FastroAI) based on your project requirements.

    Fastro (Free/Open-Source)

    Use Fastro if you want a clean, hackable FastAPI backend to build on. It includes:

    • Core Stack: FastAPI, SQLAlchemy 2.0, Pydantic v2.
    • Auth: Sessions, OAuth, and API keys.
    • Database & Admin: FastCRUD, SQLAdmin, and Alembic migrations.
    • Infrastructure: Caching, rate limiting, Taskiq jobs, and Docker (local/prod/nginx).
    • Tooling: The bp CLI for scaffolding, environment auditing, and plugins.

    FastroAI (Paid/SaaS Template)

    Use FastroAI if you are shipping a complete SaaS (AI or otherwise) and need integrated business logic. It includes everything in Fastro, plus:

    • Payments: Stripe integration (subscriptions, credits, discounts, webhooks).
    • Entitlements: Feature gating based on user plans or tiers.
    • Communication: Transactional email and notifications.
    • Frontend: Astro landing and marketing site.
    • Observability: Logfire tracing and metrics.
    • AI Agents: PydanticAI integration with memory, tools, and usage tracking.
    • Auth Extension: Adds JWT support to the existing auth layer.
    • Support: Priority support and lifetime updates.
  5. Overview of Authorization Patterns

    main

    The boilerplate uses four overlapping authorization mechanisms. You should choose the one that fits your specific use case, though they can be composed together.

    PatternWhere it livesWhen to use
    Superuser flagUser.is_superuser booleanAdmin-only operations
    Resource ownershipService-layer permission checks"Users can only edit their own X"
    Tier-based limitsTier model + RateLimit rulesSubscription gating, rate limits
    API key permissionsKeyPermission model (resource + action)Programmatic access control

    A typical request flow follows this sequence:

    1. Authentication: Session cookie or API key identifies the user.
    2. Coarse access: Superuser flag checks for admin endpoints.
    3. Fine-grained access: Service-layer ownership or tier checks.
    4. Rate limiting: Tier-based per-route limits.
  6. Understand the difference between Admin and Application Users

    main

    The boilerplate uses two completely independent authentication systems. It is critical not to confuse them:

    FeatureAdmin loginApplication users
    PurposeOperator of the SQLAdmin panelEnd users of your application
    StorageEnvironment variablesDatabase (user table)
    Auth MethodPlaintext comparison against ADMIN_PASSWORDbcrypt-hashed password via sessions
    Account LimitSingle account (one ADMIN_USERNAME)Multiple accounts (one per row)
    Access Scope/admin only/api/v1/* and /admin (to view the User model)
    Login URL/admin/login/api/v1/auth/login

    Note: An application user with is_superuser=true in the database can access superuser API endpoints, but they cannot log into the /admin panel unless their credentials match the ADMIN_USERNAME and ADMIN_PASSWORD environment variables.

  7. Implement Invalidation Strategies

    main

    The boilerplate supports three primary invalidation patterns to maintain data consistency:

    1. TTL Only (Eventually Correct): Set an expiration on the @cache decorator. Use this for read-only or near-read-only data (e.g., reference data, aggregates) where short-term staleness is acceptable.

    2. Write-Through Invalidation (Strict Consistency): Use to_invalidate_extra to define specific related keys that should be deleted when a mutation occurs. The decorator performs the deletion after the handler returns successfully.

    3. Pattern-Based Invalidation (Blast Radius): Use pattern_to_invalidate_extra to wipe groups of keys using glob patterns (e.g., user_{owner_id}_widgets:*). This is ideal for paginated lists or search results.

    Warning: pattern_to_invalidate_extra will raise PatternMatchingNotSupportedError if CACHE_BACKEND=memcached. Use Redis for pattern-based invalidation.

    # Example of combining TTL, Write-Through, and Pattern-Based invalidation
    @router.put("/{widget_id}")
    @cache(
        key_prefix="widget",
        resource_id_name="widget_id",
        expiration=900,                                       # TTL fallback (15 min)
        to_invalidate_extra={
            "widget_count": "global",
        },
        pattern_to_invalidate_extra=[
            "user_{owner_id}_widgets:*",
            "widget_search:*",
        ],
    )
    async def update_widget(request: Request, widget_id: int, owner_id: int, ...) -> dict[str, Any]:
        return await widget_service.update(widget_id, values, db)
  8. Avoid data leakage in per-user caches

    main

    When using the @cache decorator on endpoints that return user-specific data, you must ensure the cache key includes the user's identity. If you only key by a resource ID, multiple users may receive the same cached data belonging to the first user who triggered the cache.

    To fix this, include the user identifier in the key_prefix using placeholders. Alternatively, if the data is highly personalized and frequently accessed, consider not caching it at all to avoid the latency of Redis hits.

    # WRONG — every user gets user 1's data
    @router.get("/me/dashboard")
    @cache(key_prefix="dashboard", resource_id_name="user_id")
    async def my_dashboard(request: Request, user_id: int, ...):
        ...
    
    # CORRECT — Include user in the prefix
    @cache(key_prefix="dashboard_for_user_{user_id}", resource_id_name="user_id")
    # → dashboard_for_user_5:5  ← key includes user
  9. Cache anti-patterns to avoid

    main

    To maintain data integrity and performance, avoid these common caching mistakes:

    • Caching mutation responses: The decorator only caches GET requests. If you need to cache a POST or PATCH response, cache the GET endpoint that provides that data instead.
    • Caching non-derived state: Only cache data that can be reconstructed from the database. If losing the cache results in actual data loss, that data belongs in a database row, not a cache.
    • Inconsistent TTLs in pagination: Avoid having different TTLs for different pages of the same resource (e.g., widgets:page_1 expiring before widgets:page_2). Use a consistent TTL across the entire prefix family to prevent inconsistent pagination.
    • Frequent pattern invalidation: Using pattern scans to wipe many keys at once can become expensive at scale. Use them only when absolutely necessary.
  10. Configure Relationships and Avoid Circular Imports

    main

    The boilerplate uses SQLAlchemy relationship() with lazy="selectin" to prevent N+1 query problems.

    Best Practices:

    1. Avoid Circular Imports: Use TYPE_CHECKING for imports and string literals for class names in the relationship() function.
    2. Skip Unnecessary Relationships: If a relationship is only one-way (e.g., an APIKey knows its user_id but User doesn't need a list of keys), only define the ForeignKey column and skip the relationship() call. You can still perform joins via FastCRUD.
    3. Dataclass Ergonomics: Use init=False for relationship attributes so they are excluded from the model's constructor.
    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        from ..tier.models import Tier
    
    class User(Base, ...):
        tier: Mapped["Tier | None"] = relationship(
            "Tier", back_populates="users", lazy="selectin", init=False
        )
  11. Understand user security and lifecycle behaviors

    main

    Login Lockout

    The POST /api/v1/auth/login endpoint is automatically throttled by crudauth. It uses an escalating per-IP and per-identifier lockout mechanism. When throttled, the server returns a 429 status code with a Retry-After header. There are no environment variables to tune this behavior.

    Error Messages

    To prevent user enumeration, the login endpoint returns a generic "Incorrect username or password" message regardless of whether the username or the password was incorrect.

    Account Deletion

    By default, DELETE /api/v1/users/{username} performs a soft delete. Hard deletion (which involves anonymization to preserve foreign key integrity) is reserved for specific GDPR-style requests.

  12. Understand the API request lifecycle and architecture

    main

    The boilerplate follows a layered architecture to separate concerns. This allows for easier testing (mocking layers) and maintenance.

    Request Flow:

    1. HTTP Request arrives.
    2. APIRouter (modules/<feature>/routes.py): Handles HTTP concerns like status codes, schemas, and dependencies.
    3. Service (modules/<feature>/service.py): Orchestrates business rules, validation, and permission checks.
    4. FastCRUD (modules/<feature>/crud.py): Manages data access logic.
    5. SQLAlchemy Model (modules/<feature>/models.py): Defines the database schema.
    6. PostgreSQL: The persistent data store.