Daniel Milewski
Strona głównaProjektyBlogO mnieKontakt
ENPL
Daniel Milewski
Strona głównaProjektyBlogO mnieKontaktPolityka prywatności
Kanał RSS

© 2026 Daniel Milewski

JDG (CEIDG) · NIP 8442338935

  1. Strona główna/
  2. Blog/
  3. Wzorce FastAPI, Których Naprawdę Używam w Realnych Projektach

Python · FastAPI · Backend · Architektura

Wzorce FastAPI, Których Naprawdę Używam w Realnych Projektach

3 października 2024 · 3 min czytania

Na tej stronie

Struktura modułów oparta o feature’yDependency injection wszędzie tam, gdzie liczy się testowalnośćAsync SQLAlchemy z jawnym zarządzaniem sesjąUporządkowana obsługa błędówZadania w tle przez ARQ, nie FastAPI `BackgroundTasks`Konfiguracja przez Pydantic Settings

Oficjalna dokumentacja FastAPI świetnie nadaje się do startu. Ale między „zrobieniem dema” a „zbudowaniem czegoś, co zespół będzie w stanie utrzymać i rozwijać” jest jeszcze spora luka.

Poniżej są wzorce, których naprawdę używam w produkcyjnych projektach FastAPI.

Struktura modułów oparta o feature’y

Układ warstwowy (routers/, models/, services/) szybko przestaje działać, gdy codebase rośnie. Przy prostej zmianie kończysz grzebiąc w pięciu różnych plikach.

Wolę strukturę opartą o feature’y:

app/
├── auth/
│   ├── router.py
│   ├── service.py
│   ├── models.py
│   └── schemas.py
├── reports/
│   ├── router.py
│   ├── service.py
│   └── schemas.py
└── core/
    ├── config.py
    ├── database.py
    └── dependencies.py

Każdy feature odpowiada za swoje trasy, logikę biznesową, modele bazy i schematy Pydantic. Dodanie nowej funkcji oznacza nowy katalog, a nie rozrzucanie zmian po całym projekcie.

Dependency injection wszędzie tam, gdzie liczy się testowalność

System zależności FastAPI to jedna z jego największych zalet. Warto używać go szeroko:

# core/dependencies.py
async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
) -> User:
    ...

# Router feature’a
@router.get("/reports")
async def list_reports(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    ...

W testach podmieniasz zależności na lekkie fake’i działające w pamięci. Dzięki temu testy są szybkie i nie zależą od zewnętrznego stanu.

Async SQLAlchemy z jawnym zarządzaniem sesją

Używaj AsyncSession z SQLAlchemy 2.0 i zarządzaj sesją przez context manager, a nie globalny obiekt:

# core/database.py
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from contextlib import asynccontextmanager

engine = create_async_engine(settings.DATABASE_URL)

@asynccontextmanager
async def get_session() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSession(engine, expire_on_commit=False) as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

expire_on_commit=False zapobiega wygaszaniu obiektów po commit, co w asyncowych kontekstach często powoduje niepotrzebne lazy-loady i dodatkowe zapytania.

Uporządkowana obsługa błędów

Nie dopuszczaj do tego, żeby nieobsłużone wyjątki kończyły się gołym 500 i stack trace’em. Zdefiniuj własną hierarchię błędów:

class AppError(Exception):
    status_code: int = 500
    detail: str = "Internal server error"

class NotFoundError(AppError):
    status_code = 404

class PermissionError(AppError):
    status_code = 403

Zarejestruj handler na instancji aplikacji, który łapie AppError i zwraca spójny JSON. Serwisy rzucają typowane błędy, a routery pozostają czyste.

Zadania w tle przez ARQ, nie FastAPI BackgroundTasks

BackgroundTasks działa w tym samym procesie co aplikacja. Do czegokolwiek większego niż banał, np. generowania raportów, wysyłki maili czy wywołań zewnętrznych API, lepsza jest prawdziwa kolejka zadań.

ARQ (asynchroniczna kolejka Redis) dobrze integruje się z FastAPI:

from arq import create_pool

@router.post("/reports/generate")
async def generate_report(
    report_id: UUID,
    arq_pool: ArqRedis = Depends(get_arq_pool),
):
    await arq_pool.enqueue_job("generate_report_task", report_id)
    return {"status": "queued", "report_id": report_id}

Zadanie działa w osobnym workerze. API może odpowiedzieć od razu, a użytkownik dostaje identyfikator joba do odpytywania albo późniejszego powiadomienia.

Konfiguracja przez Pydantic Settings

pydantic-settings daje typowaną, walidowaną konfigurację opartą o zmienne środowiskowe:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    DATABASE_URL: str
    SECRET_KEY: str
    OPENAI_API_KEY: str
    REDIS_URL: str = "redis://localhost:6379"
    DEBUG: bool = False

    class Config:
        env_file = ".env"

settings = Settings()

Koniec z rozsianym po projekcie os.getenv("THING", "default"). Konfiguracja jest jawna, typowana i sprawdzana już przy starcie aplikacji.


To nie są rewolucyjne pomysły. To po prostu pragmatyczne decyzje, które sprawdziły się u mnie w kilku wdrożeniach produkcyjnych. Celem jest codebase, który da się zrozumieć, testować i bez bólu przekazać innemu inżynierowi.

Jeśli startujesz nowy projekt w FastAPI i chcesz przegadać architekturę, chętnie pomogę.

Powiązane projekty

InvestTracker — platforma do analityki majątku i portfela inwestycyjnego

Żadne istniejące narzędzie nie potrafiło poprawnie połączyć polskich rachunków inwestycyjnych (IKE, IKZE, OIPE, Finax) z zagranicznymi akcjami, ETF-ami, kryptowalutami i metalami szlachetnymi w jednym, wiarygodnym dashboardzie.

Powiązane wpisy

  • lis 2025

    Koszt nabycia metodą FIFO w Pythonie: czego nauczył mnie InvestTracker

    3 min czytania
  • lis 2024

    Praktyczne Lekcje z Budowania Aplikacji LLM na Produkcji

    3 min czytania
Wszystkie wpisy

Szukasz programisty Pythona?

Wyślij mi opis roli albo problemu. Odpowiadam w ciągu kilku dni roboczych, po polsku lub angielsku.

danielmilewski123@gmail.com
LinkedInGitHubXFormularz kontaktowy