Was ist FastAPI?
FastAPI ist ein modernes Python-Web-Framework zum Erstellen von APIs. Die Kernidee ist, dass Ihre Type Hints den Vertrag bilden. Deklarieren Sie einen Parameter als int, einen Body als Pydantic-Modell oder einen Rückgabetyp als ItemOut, und das Framework validiert die Anfrage, serialisiert die Antwort und schreibt die Dokumentation – alles basierend auf dieser einen Deklaration.
Es basiert auf zwei Bibliotheken, deren Namen Sie kennen sollten. Starlette stellt das ASGI-Toolkit bereit: Routing, middleware und den Request/Response-Zyklus. Pydantic übernimmt die Validierung und Serialisierung. FastAPI ist die Schicht, die beides kombiniert, Dependency Injection hinzufügt und beim Start ein OpenAPI-Schema generiert.
Das Ergebnis ist, dass eine kleine, lesbare Funktion Ihnen einen validierten Endpunkt mit interaktiver Dokumentation liefert. Die Magie ist echt, aber sie ist mechanisch – und genau darum geht es in diesem Guide: die Mechanik dahinter zu verstehen.
Path-Operationen
Eine Path-Operation ist eine Funktion, die an eine HTTP-Methode und eine URL gebunden ist. Der Decorator benennt die Methode, und die Funktionssignatur teilt FastAPI mit, was zu erwarten ist.
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, world"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
Das Segment {item_id} ist ein Path-Parameter, und die Annotation int bedeutet, dass FastAPI den String automatisch konvertiert und bei einem Fehler einen 422-Statuscode zurückgibt. Alles, was nicht im Pfad enthalten ist, wird als Query-Parameter behandelt, wobei ein Standardwert von None ihn optional macht. Es ist kein manuelles Parsing und kein request.args erforderlich.
Gruppieren Sie zusammengehörige Operationen mit einem APIRouter, der ein Präfix und Tags für die Dokumentation enthalten kann.
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["items"])
@router.get("")
async def list_items():
return []
@router.post("", status_code=201)
async def create_item():
return {}
Binden Sie den Router anschließend in die App ein: app.include_router(router). Auf diese Weise bleibt ein FastAPI-Projekt modular.
Pydantic-Models
Ein Pydantic-Model ist eine Klasse, die von BaseModel erbt. Es parst den Input, erzwingt kompatible Typen, wendet Constraints an und wirft einen präzisen Validierungsfehler, wenn etwas nicht stimmt.
# app/schemas.py
from datetime import datetime
from pydantic import BaseModel, Field
class ItemBase(BaseModel):
name: str = Field(min_length=1, max_length=120)
price: float = Field(gt=0)
tags: list[str] = []
class ItemCreate(ItemBase):
pass
class ItemOut(ItemBase):
id: int
created_at: datetime
model_config = {"from_attributes": True}
Field fügt Constraints und Metadaten hinzu. model_config = {"from_attributes": True} ermöglicht es Pydantic, Attribute aus einem ORM-Objekt zu lesen, sodass Sie eine Datenbankzeile direkt zurückgeben und serialisieren lassen können. Die Trennung von ItemCreate und ItemOut ist beabsichtigt: Das Input-Model hat keine id, und das Output-Model kann Felder exponieren, die der Client nicht setzen darf.
Verwenden Sie ein Pydantic-Model als Body-Typ, und FastAPI parst, validiert und überreicht Ihnen ein typisiertes Objekt.
@app.post("/items", response_model=ItemOut, status_code=201)
async def create_item(item: ItemCreate):
return await repo.create(item)
Wenn der Body ungültig ist, gibt FastAPI einen 422-Fehler mit einer Liste der exakten Felder zurück, die die Validierung nicht bestanden haben. Sie mussten dafür keine einzige Zeile Validierungslogik schreiben.
Request-Bodies und Validierung
FastAPI unterscheidet die Datenquellen anhand von Position und Typ:
- Path-Parameter kommen aus der URL.
- Query-Parameter sind alles andere in der Signatur.
- Ein Pydantic-Modell stellt den Request-Body dar.
Header,Cookie,FormundFilemarkieren diese speziellen Inputs.
from fastapi import Header, Query
@app.get("/search")
async def search(
q: str = Query(min_length=1, max_length=50),
limit: int = Query(default=20, ge=1, le=100),
user_agent: str | None = Header(default=None),
):
return {"q": q, "limit": limit, "user_agent": user_agent}
Constraints für Query-Parameter werden auf die gleiche Weise erzwungen wie bei Modellen, und die generierte Dokumentation zeigt die entsprechenden Limits an. Diese Konsistenz ist der Grund, warum FastAPI-Code so kompakt geschrieben ist.
Automatische Dokumentation
Da das Schema direkt aus dem Code generiert wird, ist die Dokumentation niemals veraltet. Starte die App und öffne /docs für das Swagger UI, wo du Endpunkte direkt aufrufen kannst, oder /redoc für eine übersichtlichere Referenz.
app = FastAPI(
title="Inventory API",
version="1.0.0",
description="Items, stock levels and orders.",
)
Du kannst Zusammenfassungen, Beschreibungen und Beispiele zu Path Operations und Feldern hinzufügen, die dann automatisch in der Dokumentation erscheinen. Für Frontend-Teams kann dasselbe Schema typisierte Clients generieren, sodass die API und ihre Konsumenten synchron bleiben. Das ist der große Vorteil dabei, Typen als Vertrag (Contract) zu betrachten.
Abhängigkeiten
Eine Dependency ist eine Funktion, die FastAPI vor deinem Endpoint aufruft und deren Rückgabewert injiziert wird. Depends() ist der zugrunde liegende Mechanismus, und er ist komponierbar.
from fastapi import Depends, HTTPException
async def get_session() -> AsyncSession:
async with SessionLocal() as session:
yield session
async def get_current_user(
token: str = Depends(get_token),
session: AsyncSession = Depends(get_session),
) -> User:
user = await authenticate(session, token)
if user is None:
raise HTTPException(status_code=401, detail="Not authenticated")
return user
@app.get("/me", response_model=UserOut)
async def read_me(user: User = Depends(get_current_user)):
return user
Dependencies können synchron oder asynchron sein, können yield nutzen, um nach der Antwort ein Cleanup durchzuführen, und werden innerhalb eines Requests gecacht, sodass get_session nur einmal aufgerufen wird, selbst wenn mehrere Dependencies darauf zugreifen. Sie sind der idiomatische Ort für Datenbank-Sessions, Authentifizierung, Pagination und Feature-Flags.
Async versus sync
FastAPI unterstützt sowohl def als auch async def Endpunkte, und die Wahl hat einen entscheidenden Einfluss.
- Ein
async defEndpunkt wird auf dem Event Loop ausgeführt. Jeder I/O-Aufruf darin muss über eine async-Library mitawaitaufgerufen werden, andernfalls wird der Loop für alle anderen Anfragen blockiert. - Ein einfacher
defEndpunkt wird in einem Thread Pool ausgeführt. Blockierende Aufrufe sind hier also sicher, allerdings belegt jede Anfrage einen eigenen Thread.
# Async: use an async driver and await it
@app.get("/items")
async def list_items(session: AsyncSession = Depends(get_session)):
result = await session.execute(select(Item))
return result.scalars().all()
# Sync: blocking code is fine; FastAPI offloads it
@app.get("/report")
def build_report():
return generate_report() # CPU or blocking I/O
Die Regel ist simpel: Entweder konsequent async oder konsequent sync. Das Mischen von blockierenden Aufrufen in einem async def Endpunkt ist der häufigste Performance-Bug in FastAPI – und er bleibt unsichtbar, bis die ersten Nutzer auf die Seite kommen.
Fehler und Statuscodes
Werfen Sie HTTPException, um einen Fehler mit einem Statuscode und JSON-Details zurückzugeben. FastAPI wandelt dies in eine Antwort um und dokumentiert den Statuscode.
from fastapi import HTTPException, status
@app.get("/items/{item_id}", response_model=ItemOut)
async def read_item(item_id: int):
item = await repo.get(item_id)
if item is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
return item
Um eine konsistente Fehlerstruktur über die gesamte API hinweg zu gewährleisten, sollten Sie Exception-Handler registrieren, die Framework- und Domain-Fehler in ein einheitliches Format (Envelope) überführen. Halten Sie das Feld detail stabil, da die Clients dieses parsen werden.
Hintergrundaufgaben
BackgroundTasks führt Aufgaben im selben Prozess aus, nachdem die Antwort gesendet wurde. Dies ist die leichtgewichtige Option, um beispielsweise eine E-Mail zu versenden oder einen Audit-Eintrag zu schreiben.
from fastapi import BackgroundTasks
def send_receipt(email: str) -> None:
...
@app.post("/orders", status_code=201)
async def create_order(
order: OrderCreate,
tasks: BackgroundTasks,
):
saved = await repo.create(order)
tasks.add_task(send_receipt, saved.email)
return saved
Hintergrundaufgaben sind keine Job-Queue. Sie laufen im selben Prozess (in-process), weshalb sie bei einem Neustart des Workers verloren gehen und mit der Verarbeitung von Anfragen konkurrieren. Für alles, was dauerhaft (durable) sein muss, nutzen Sie stattdessen Celery, RQ oder eine managed Queue.
Datenbanken
FastAPI besitzt keinen eigenen Datenbank-Layer, daher müssen Sie diesen selbst wählen. Die gängigsten Optionen sind SQLAlchemy mit einer async engine oder SQLModel, welches SQLAlchemy mit Pydantic kombiniert. Das Dependency-Pattern stellt die Session bereit und schließt diese nach dem Request wieder.
# app/db.py
from collections.abc import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
engine = create_async_engine(settings.database_url, pool_pre_ping=True)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
async def get_session() -> AsyncIterator[AsyncSession]:
async with SessionLocal() as session:
yield session
@router.post("", response_model=ItemOut, status_code=201)
async def create_item(
payload: ItemCreate,
session: AsyncSession = Depends(get_session),
):
item = Item(**payload.model_dump())
session.add(item)
await session.commit()
await session.refresh(item)
return item
Verwenden Sie expire_on_commit=False, damit Objekte nach dem Commit nutzbar bleiben, und halten Sie Transaktionen innerhalb des Requests. Wenn Sie einen synchronen Treiber bevorzugen, nutzen Sie einfache def Endpunkte, damit FastAPI diese in den Thread-Pool auslagern kann.
Sicherheit
FastAPI liefert die Bausteine für OAuth2 und JWT, ohne ein vollständiges Authentifizierungssystem vorzuschreiben. OAuth2PasswordBearer extrahiert den Token, und python-jose oder pyjwt verifiziert ihn.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/users/me", response_model=UserOut)
async def read_me(
token: str = Depends(oauth2_scheme),
session: AsyncSession = Depends(get_session),
):
user = await get_user_from_token(session, token)
if user is None:
raise HTTPException(status_code=401, detail="Invalid token")
return user
Speichere Passwort-Hashes mit passlib oder argon2-cffi, niemals im Klartext. Führe das Hashen und Verifizieren außerhalb des Endpunkts aus, bewahre Secrets in den Einstellungen auf und implementiere Rate Limiting sowie CORS bewusst. FastAPI stellt dir die Werkzeuge bereit; die Richtlinien musst du selbst definieren.
Konfiguration und Einstellungen
Verwalten Sie die Konfiguration in einem einzigen typisierten Objekt, anstatt Umgebungsvariablen im gesamten Code zu lesen. pydantic-settings validiert die Umgebung beim Start und bietet Ihnen dieselbe Typsicherheit wie Ihre Modelle.
# app/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
database_url: str
secret_key: str
access_token_minutes: int = 30
settings = Settings()
from functools import lru_cache
from app.config import Settings
@lru_cache
def get_settings() -> Settings:
return Settings()
@app.get("/info")
async def info(settings: Settings = Depends(get_settings)):
return {"token_minutes": settings.access_token_minutes}
Da die Einstellungen wie jede andere Abhängigkeit injiziert werden, können Tests diese mit app.dependency_overrides[get_settings] überschreiben. Fehlende erforderliche Variablen führen bereits beim Start zu einem Fehler und nicht erst bei der ersten Anfrage – genau dann, wenn man es wissen möchte.
Testing
TestClient kapselt die App in einen WSGI-ähnlichen Client und führt echte Anfragen im selben Prozess aus, einschließlich Validierung und Dependency Overrides.
# tests/test_items.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_create_item():
response = client.post("/items", json={"name": "Cup", "price": 9.5})
assert response.status_code == 201
assert response.json()["name"] == "Cup"
def test_invalid_price():
response = client.post("/items", json={"name": "Cup", "price": -1})
assert response.status_code == 422
Verwenden Sie app.dependency_overrides, um die Datenbank-Session gegen eine Test-Session auszutauschen, und schreiben Sie asynchrone Tests mit httpx.AsyncClient, wenn Sie asynchrone Endpunkte direkt testen müssen. Da Dependencies explizit definiert sind, ist deren Ersetzung in Tests unkompliziert.
Best Practices
- Deklarieren Sie Request- und Response-Modelle explizit; akzeptieren oder geben Sie niemals einfache Dictionaries zurück.
- Halten Sie
async def-Endpoints vollständig async oder verwenden Siedef, damit FastAPI blockierende Aufgaben auslagert. - Platzieren Sie gemeinsam genutzte Logik in
Depends()-Dependencies, anstatt sie in jeder Route zu wiederholen. - Strukturieren Sie die App mit
APIRouterund weisen Sie jedem Router ein Prefix sowie Tags zu. - Fügen Sie Constraints mit
FieldundQueryhinzu, anstatt die Validierung manuell durchzuführen. - Werfen Sie
HTTPExceptionmit dem passenden Statuscode und einem stabilendetail. - Überschreiben Sie Dependencies in Tests, anstatt interne Funktionen zu patchen.
- Speichern Sie Secrets in Settings-Objekten, die aus der Umgebung ausgelesen werden.
Häufige Fehler
- Aufruf von blockierendem Code innerhalb eines
async def-Endpoints, wodurch der Event Loop blockiert wird. - Rückgabe von ORM-Objekten ohne
response_model, wodurch interne Spalten offengelegt werden. - Vergessen von
from_attributes = Trueund anschließendes Rätseln, warum die Serialisierung fehlschlägt. - Schreiben eines einzigen riesigen
main.pyanstelle von Routern und Modulen. - Verwendung von
BackgroundTasksals dauerhafte Job-Queue. - Teilen einer einzigen Session über mehrere Requests oder asynchrone Tasks hinweg.
- Zweifache Validierung desselben Inputs, weil das Pydantic-Modell ignoriert wurde.
- Abfangen jeder Exception und Rückgabe eines 200-Status mit einem Error-Body.
Wie geht es weiter?
FastAPI verwandelt Type Hints in eine validierte und dokumentierte API, weshalb es zum Standard für neue Python-Services geworden ist. Um die Vor- und Nachteile abzuwägen, lesen Sie den Flask-Guide für den minimalen synchronen Ansatz und den zu Django für ein vollständiges Framework mit ORM und Admin-Bereich. Vertiefen Sie anschließend Ihr Wissen über API-Kontrakte mit OpenAPI und die Design-Regeln in REST APIs. Stellen Sie zudem sicher, dass Ihre Endpunkte eine Dokumentation wert sind, indem Sie das Ressourcenmodell korrekt aufsetzen.