FastAPI: REST API v Pythonu od nuly krok za krokem
Postavte funkční REST API ve FastAPI – endpointy, validace přes Pydantic, stavové kódy, závislosti a automatická dokumentace. S kódem, který rovnou spustíte.
FastAPI je webový framework pro tvorbu API v Pythonu postavený na typových anotacích. Z typů odvodí validaci vstupu, serializaci výstupu i interaktivní dokumentaci. V tomto návodu postavíme jednoduché API pro správu úkolů (todo) – od instalace po testování.
Instalace
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
Extra standard přidá server Uvicorn a CLI příkaz fastapi (dostupný od FastAPI 0.111).
První endpoint
Vytvořte main.py:
from fastapi import FastAPI
app = FastAPI(title="Todo API")
@app.get("/")
def root():
return {"status": "ok"}
Spuštění ve vývojovém režimu s automatickým reloadem:
fastapi dev main.py
Otevřete http://127.0.0.1:8000/docs – uvidíte Swagger UI s vaším endpointem. Alternativní dokumentace běží na /redoc.
Modely a validace
Data popíšeme Pydantic modely. Oddělíme model pro vstup (co posílá klient) a pro výstup (co vracíme):
from pydantic import BaseModel, Field
class TodoCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
done: bool = False
class Todo(TodoCreate):
id: int
Když klient pošle prázdný title nebo místo bool text, FastAPI automaticky vrátí 422 Unprocessable Entity s popisem chyby. Nemusíte psát žádné if.
CRUD endpointy
Pro jednoduchost ukládáme do slovníku v paměti:
from fastapi import FastAPI, HTTPException, status
app = FastAPI(title="Todo API")
db: dict[int, Todo] = {}
next_id = 1
@app.get("/todos", response_model=list[Todo])
def list_todos(done: bool | None = None):
items = list(db.values())
if done is not None:
items = [t for t in items if t.done == done]
return items
@app.get("/todos/{todo_id}", response_model=Todo)
def get_todo(todo_id: int):
if todo_id not in db:
raise HTTPException(status_code=404, detail="Úkol nenalezen")
return db[todo_id]
@app.post("/todos", response_model=Todo, status_code=status.HTTP_201_CREATED)
def create_todo(data: TodoCreate):
global next_id
todo = Todo(id=next_id, **data.model_dump())
db[next_id] = todo
next_id += 1
return todo
@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, data: TodoCreate):
if todo_id not in db:
raise HTTPException(status_code=404, detail="Úkol nenalezen")
db[todo_id] = Todo(id=todo_id, **data.model_dump())
return db[todo_id]
@app.delete("/todos/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_todo(todo_id: int):
if db.pop(todo_id, None) is None:
raise HTTPException(status_code=404, detail="Úkol nenalezen")
Všimněte si, jak FastAPI rozlišuje parametry:
todo_idje v cestě ({todo_id}) → path parametr, převede se naint.donev cestě není a je to jednoduchý typ → query parametr (/todos?done=true).data: TodoCreateje Pydantic model → tělo požadavku (JSON).
Vyzkoušení přes curl
curl -X POST localhost:8000/todos \
-H "Content-Type: application/json" \
-d '{"title": "Napsat API"}'
curl localhost:8000/todos?done=false
curl -X DELETE localhost:8000/todos/1 -i
Závislosti (Depends)
Systém závislostí je jedna z nejsilnějších funkcí FastAPI. Hodí se pro autentizaci, připojení k databázi nebo sdílené parametry:
from typing import Annotated
from fastapi import Depends, Header
def require_api_key(x_api_key: Annotated[str, Header()]):
if x_api_key != "tajny-klic":
raise HTTPException(status_code=401, detail="Neplatný klíč")
@app.delete("/todos", dependencies=[Depends(require_api_key)])
def clear_todos():
db.clear()
return {"deleted": True}
Hlavička X-API-Key se z názvu parametru odvodí automaticky (podtržítka → pomlčky). Klíč samozřejmě v reálu načtěte z proměnné prostředí, ne z kódu.
Testování
FastAPI má TestClient postavený na httpx:
# test_main.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_and_get():
r = client.post("/todos", json={"title": "Test"})
assert r.status_code == 201
todo_id = r.json()["id"]
assert client.get(f"/todos/{todo_id}").json()["title"] == "Test"
def test_validation():
r = client.post("/todos", json={"title": ""})
assert r.status_code == 422
pip install pytest
pytest -q
sync, nebo async?
Funkce můžete psát jako def i async def. Obyčejné def FastAPI spouští ve vlákně z poolu, takže blokující kód (např. synchronní databázový ovladač) nezablokuje server. async def používejte jen tehdy, když uvnitř opravdu voláte await – blokující volání v async def zastaví celou smyčku událostí.
Další kroky
- Nahraďte slovník databází (SQLite přes SQLModel nebo SQLAlchemy).
- Přidejte CORS přes
fastapi.middleware.cors.CORSMiddleware, pokud API volá frontend z jiné domény. - Pro produkci spusťte
fastapi run main.pynebo Uvicorn za reverzní proxy.
Kód z tohoto návodu si můžete nahrát do Projektu v ColdHost AI a nechat si v režimu Programování doplnit například perzistenci do SQLite – AI upraví přímo soubory a výsledek si stáhnete jako ZIP.