Na rozmowach z FastAPI najczęściej pada kilkanaście pytań: czym FastAPI różni się od Flaska i Django, jak działa walidacja przez Pydantic, czym różni sięasync defod zwykłegodef, czym jestDependsiresponse_modeloraz jak się je testuje. Rekruter nie sprawdza, czy znasz całą dokumentację, tylko czy rozumiesz, co dzieje się z żądaniem od przyjścia do odpowiedzi. Dobra odpowiedź to jedno zdanie definicji i krótki przykład kodu.
FastAPI szybko stał się jednym z najczęściej wybieranych frameworków do budowy API w Pythonie, więc pojawia się w ofertach na stanowisko juniora coraz częściej. Rozmowa o nim rzadko jest egzaminem z dokumentacji. Najczęściej to te same kilkanaście tematów, przy których rekruter sprawdza, czy rozumiesz, jak framework obsługuje żądanie, a nie czy pamiętasz nazwy dekoratorów. Poniżej znajdziesz pytania, które wracają na rozmowach, razem z tym, jak na nie odpowiadać. Jeśli dopiero zaczynasz, zacznij od artykułu o tym, czym jest FastAPI i jak stworzyć pierwsze API, a ten tekst potraktuj jako listę kontrolną. Sam język sprawdzimy w pytaniach rekrutacyjnych z Pythona, a framework o innej filozofii w artykule o pytaniach z Django. Mikroframework o podobnym przeznaczeniu opisujemy w tekście o pytaniach z Flaska.
Przykłady z tego artykułu sprawdziliśmy w działającej aplikacji na FastAPI 0.162 z Pydantic 2.14 i Pythonem 3.13: testowaliśmy walidację, zależności, uwierzytelnianie, CORS, zadania w tle i zachowanie przy blokujących funkcjach. Wyniki w tekście, np. czasy odpowiedzi i kody statusu, pochodzą z tych uruchomień.
Jak odpowiadać na pytania rekrutacyjne z FastAPI?
Najlepiej w trzech krokach: jedno zdanie definicji, krótki przykład (kilka linijek kodu albo sytuacja z projektu) i informacja, kiedy to ma znaczenie w praktyce. Taka odpowiedź zajmuje 30–60 sekund i pokazuje, że rozumiesz mechanizm, a nie wkułeś formułkę. Jeśli czegoś nie wiesz, powiedz, co wiesz, i rozumuj na głos. Rekruterzy oceniają sposób myślenia, a nie tylko wynik.
Pytania są ułożone od podstaw do tematów, które częściej pojawiają się przy ofertach na mid-poziom.
Podstawy FastAPI
Czym jest FastAPI i czym różni się od Flaska i Django?
FastAPI to nowoczesny framework do budowy API w Pythonie, oparty na adnotacjach typów (type hints), bibliotece Pydantic do walidacji danych i na asynchronicznym standardzie ASGI. Flask jest minimalistyczny i nie narzuca niczego poza routingiem, a Django to duży framework „z bateriami” z ORM, panelem administracyjnym i szablonami. FastAPI leży pośrodku: daje walidację, dokumentację API i wstrzykiwanie zależności od razu, ale bazę danych i resztę dobierasz sam. Szersze porównanie znajdziesz w artykule Django, Flask czy FastAPI.
Dlaczego FastAPI uchodzi za szybki?
Z dwóch powodów: działa na asynchronicznym serwerze (np. Uvicorn) i nie traci czasu na czekanie przy operacjach wejścia-wyjścia, a walidacja przez Pydantic jest zaimplementowana w szybkim rdzeniu. Nie oznacza to jednak, że każdy endpoint jest szybszy niż we Flasku. Przewaga pojawia się tam, gdzie aplikacja dużo czeka (baza, inne API), i wymaga poprawnego użycia async, o czym niżej.
Skąd FastAPI bierze dokumentację API?
Generuje ją automatycznie ze schematu OpenAPI, który tworzy na podstawie Twoich tras, adnotacji typów i modeli Pydantic. W teście /docs (Swagger UI), /redoc i /openapi.json zwróciły status 200, a ścieżka /users była w schemacie bez żadnej dodatkowej konfiguracji. To dlatego opis parametrów i typów trzeba utrzymywać w kodzie, a nie w osobnym pliku.
Jak uruchomić aplikację FastAPI?
Aplikacja to zwykły obiekt FastAPI(), który uruchamia serwer ASGI. W pracy lokalnej używa się polecenia fastapi dev (z automatycznym przeładowaniem), a na produkcji serwera Uvicorn, często z kilkoma procesami roboczymi (workers).
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
Skąd bierze się błąd „Error loading ASGI app. Could not import module”?
To jeden z najczęściej zgłaszanych błędów początkujących. Polecenie uvicorn nazwa_modułu:nazwa_obiektu musi wskazywać plik Pythona i zmienną z aplikacją. W teście uvicorn api:app w katalogu bez pliku api.py zakończyło się komunikatem Error loading ASGI app. Could not import module "api". Sprawdź więc trzy rzeczy: czy uruchamiasz polecenie z właściwego katalogu, czy plik nazywa się tak, jak podałeś, i czy zmienna z aplikacją ma nazwę z drugiej części (app).
Parametry, walidacja i Pydantic
Czym różni się parametr ścieżki od parametru zapytania i od ciała żądania?
| Rodzaj | Skąd pochodzi | Jak deklarujesz |
|---|---|---|
| Parametr ścieżki (path) | część adresu: /users/5 | nazwa w ścieżce i argument funkcji: user_id: int |
| Parametr zapytania (query) | po znaku ?: /items?limit=5 | argument funkcji spoza ścieżki: limit: int = 10 |
| Ciało żądania (body) | JSON w treści POST/PUT | argument typu modelu Pydantic: user: UserIn |
FastAPI rozpoznaje rodzaj sam, po tym, czy nazwa występuje w ścieżce i jakiego typu jest argument.
Jak działa walidacja danych i co dostaje klient, gdy dane są błędne?
FastAPI sprawdza typy i ograniczenia z modeli Pydantic, zanim wywoła funkcję. Przy błędzie zwraca kod 422 z listą problemów. W teście żądanie z imieniem „J”, wiekiem 0 i brakiem hasła dało 422 z trzema błędami: string_too_short dla name, greater_than dla age i missing dla password, każdy z dokładną lokalizacją (loc). Podobnie /users/abc przy user_id: int oraz limit=500 przy Query(le=100) skończyły się kodem 422. Szczegóły modeli wejścia i wyjścia opisujemy w artykule o walidacji danych w FastAPI z Pydantic.
from pydantic import BaseModel, Field
class UserIn(BaseModel):
name: str = Field(min_length=2)
age: int = Field(gt=0)
password: str
Do czego służy response_model?
Określa kształt odpowiedzi: FastAPI filtruje zwracane dane do pól modelu i dokumentuje je w OpenAPI. Najważniejsze zastosowanie to ochrona przed wyciekiem. W teście ta sama funkcja zwracała słownik z hasłem: z response_model=UserOut klient dostał tylko id i name, a bez niego także password. Dlatego modele wejścia i wyjścia warto rozdzielać.
class UserOut(BaseModel):
id: int
name: str
@app.post("/users", response_model=UserOut, status_code=201)
def create_user(user: UserIn):
return {"id": 1, **user.model_dump()}
Jak zwrócić własny kod statusu i błąd?
Kod sukcesu ustawiasz w dekoratorze (status_code=201), a błąd zgłaszasz przez HTTPException. W teście HTTPException(status_code=404, detail="Nie ma takiego użytkownika") zwróciło 404 z ciałem {"detail": "Nie ma takiego użytkownika"}.
from fastapi import HTTPException
@app.get("/users/{user_id}")
def get_user(user_id: int):
if user_id != 1:
raise HTTPException(status_code=404, detail="Nie ma takiego użytkownika")
return {"id": user_id}
Skąd bierze się odpowiedź 307 Temporary Redirect?
Pojawia się, gdy trasa jest zdefiniowana z ukośnikiem na końcu (/items/), a klient wywołuje adres bez niego (/items). FastAPI odpowiada wtedy przekierowaniem na wersję z ukośnikiem. W teście /items zwróciło 307 z nagłówkiem location wskazującym /items/, a /items/ zwróciło 200. Spójność jest ważna: wybierz jedną formę w trasach i w kliencie. Na Stack Overflow ten problem zgłaszany jest często razem z serwerem za proxy, gdzie przekierowanie wskazuje zły adres.
Czy kolejność tras ma znaczenie?
Tak. FastAPI dopasowuje trasy w kolejności ich definicji, więc trasa ogólna (/users/{user_id}) zdefiniowana przed szczegółową (/users/me) przejmie żądanie, a me zostanie potraktowane jako user_id. W naszej aplikacji /users/me było zdefiniowane wcześniej i zwróciło własną odpowiedź. Kolejność odwrotną opisujemy z dokumentacji, nie testowaliśmy jej.
Asynchroniczność
Czym różni się async def od zwykłego def w endpoincie?
Endpoint async def działa bezpośrednio w pętli zdarzeń (event loop) i nie wolno w nim wołać funkcji blokujących. Zwykły def FastAPI uruchamia w osobnym wątku z puli, więc blokujące wywołanie nie zatrzymuje reszty aplikacji. Zasada: async def używaj, gdy w środku jest await (asynchroniczna baza, klient HTTP), w pozostałych przypadkach zwykły def.
Co się stanie, jeśli w async def wywołam funkcję blokującą?
Zablokujesz całą pętlę zdarzeń, więc inne żądania muszą czekać. Pokazuje to test z dwoma równoległymi żądaniami do endpointu, który „pracuje” 0,5 s:
| Endpoint | Czas dwóch równoległych żądań |
|---|---|
async def z time.sleep(0.5) | 1,0 s (żądania wykonały się po kolei) |
zwykłe def z time.sleep(0.5) | 0,5 s (osobne wątki) |
async def z await asyncio.sleep(0.5) | 0,5 s (pętla nie była zablokowana) |
To klasyczny błąd juniorów: async def wygląda na „szybszy”, a ze zwykłym requests.get albo time.sleep w środku jest wolniejszy niż zwykły def. Praktyczne zastosowanie async w aplikacjach z modelami językowymi opisujemy w artykule o FastAPI, async i streamingu LLM.
import asyncio
@app.get("/ok")
async def ok():
await asyncio.sleep(0.5) # nie blokuje pętli zdarzeń
return {"ok": True}
@app.get("/blocking")
def blocking():
import time
time.sleep(0.5) # zwykły def: osobny wątek
return {"ok": True}
Zależności i bezpieczeństwo
Czym jest Depends i po co wstrzykiwanie zależności?
Depends to mechanizm wstrzykiwania zależności (dependency injection): wskazujesz funkcję, a FastAPI wywoła ją przed endpointem i poda wynik jako argument. Dzięki temu logikę wspólną dla wielu tras (sesja bazy, aktualny użytkownik, paginacja) trzymasz w jednym miejscu, a zależności możesz podmienić w testach.
from typing import Annotated
from fastapi import Depends
def get_db():
db = "połączenie"
try:
yield db
finally:
pass # tu zamykasz połączenie
@app.get("/items")
def read_items(db: Annotated[str, Depends(get_db)]):
return {"db": db}
Do czego służy yield w zależności?
Kod przed yield wykona się przed endpointem, a kod po yield (w finally) po jego zakończeniu, więc idealnie nadaje się do otwierania i zamykania zasobów, np. sesji bazy. W teście kolejność wywołań to open, handler, close. O tym, jak ten wzorzec wygląda z prawdziwą bazą, piszemy w artykule o SQLAlchemy 2 z FastAPI.
Jak podmienić zależność w testach?
Przez app.dependency_overrides: w teście wskazujesz funkcję zastępczą, a po teście czyścisz słownik. W naszym teście podmiana get_db na lambda: "fake-db" sprawiła, że endpoint dostał nową wartość bez żadnych zmian w kodzie aplikacji.
from fastapi.testclient import TestClient
app.dependency_overrides[get_db] = lambda: "fake-db"
client = TestClient(app)
assert client.get("/items").json() == {"db": "fake-db"}
app.dependency_overrides.clear()
Jak działa uwierzytelnianie w FastAPI?
FastAPI dostarcza narzędzia, np. OAuth2PasswordBearer, które wyciągają token z nagłówka Authorization: Bearer …. Sama weryfikacja tokenu (np. JWT) i sprawdzenie użytkownika to Twój kod w zależności. W teście brak nagłówka dał 401 z komunikatem Not authenticated, a z nagłówkiem Bearer abc token trafił do funkcji. Pełny przykład z logowaniem i JWT znajdziesz w artykule o uwierzytelnianiu w FastAPI, a o tym, żeby użytkownik widział tylko swoje dane, w tekście o autoryzacji w FastAPI.
Czym jest CORS i jak go skonfigurować?
CORS to mechanizm przeglądarki, który blokuje wywołania API z innej domeny, jeśli serwer nie zezwoli na nie nagłówkiem Access-Control-Allow-Origin. W FastAPI dodajesz CORSMiddleware z listą dozwolonych źródeł. W teście żądanie z https://example.com dostało odpowiedni nagłówek, a z https://evil.com nagłówka nie dostało. Nie zezwalaj na wszystkie źródła (*) razem z danymi uwierzytelniającymi.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_methods=["*"],
allow_headers=["*"],
)
Struktura i praktyka
Do czego służy APIRouter?
Pozwala podzielić aplikację na moduły: każdy ma własny router z prefiksem i tagami, a główny plik tylko je dołącza przez app.include_router. W teście router z prefiksem /api/v1 odpowiedział na /api/v1/ping.
from fastapi import APIRouter
router = APIRouter(prefix="/api/v1", tags=["v1"])
@router.get("/ping")
def ping():
return {"pong": True}
app.include_router(router)
Co to są BackgroundTasks i kiedy ich użyć?
To zadania, które wykonują się po wysłaniu odpowiedzi, np. wysłanie maila potwierdzającego. W teście endpoint zwrócił odpowiedź, a zadanie dopisało wpis po handlerze (kolejność: handler, potem po odpowiedzi). To rozwiązanie na krótkie, mało krytyczne zadania. Do ciężkich i długich prac potrzebna jest kolejka zadań, bo BackgroundTasks działają w tym samym procesie.
Jak obsłużyć start i zamknięcie aplikacji (lifespan)?
Przez funkcję lifespan z yield: kod przed yield wykonuje się przy starcie (np. nawiązanie połączeń), a po nim przy zamknięciu. W teście zdarzenia to kolejno start i stop. To nowocześniejsza alternatywa dla starszych dekoratorów on_event.
Jak globalnie obsłużyć wyjątki?
Przez @app.exception_handler: rejestrujesz funkcję dla danego typu wyjątku, a FastAPI użyje jej zamiast domyślnej odpowiedzi 500. Dzięki temu własne błędy domenowe zamieniasz na spójne odpowiedzi JSON w jednym miejscu, a nie w każdym endpoincie. W teście wyjątek AppError obsłużony przez własny handler dał kod 418 i ciało {"error": "coś poszło nie tak"}.
from fastapi import Request
from fastapi.responses import JSONResponse
class AppError(Exception):
def __init__(self, msg: str):
self.msg = msg
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(status_code=418, content={"error": exc.msg})
Jak działa aplikacja z kilkoma workerami i czy współdzielą stan?
Każdy worker to osobny proces z własną pamięcią. W teście aplikacja uruchomiona przez uvicorn --workers 2 z globalnym licznikiem odpowiadała z dwóch procesów (różne pid), a każdy miał własny, niezależny licznik. Dlatego stanu, który ma być wspólny (sesje, liczniki, cache), nie trzymasz w zmiennej globalnej, tylko w zewnętrznym magazynie, np. w bazie albo Redisie. Uvicorn potrafi sam uruchomić kilka procesów, a częsta alternatywa to Gunicorn jako menedżer procesów z workerami Uvicorna. Różnice między tymi konfiguracjami opisujemy z dokumentacji, nie testowaliśmy ich.
Jak przyjąć plik w FastAPI?
Przez UploadFile i File. Walidację rozmiaru i typu oraz bezpieczny zapis pokazujemy w artykule o uploadzie plików w FastAPI.
Jak testuje się aplikację FastAPI?
Przez TestClient, który wywołuje aplikację bez uruchamiania serwera, zwykle razem z pytest. W połączeniu z dependency_overrides pozwala zastąpić bazę lub zewnętrzne API atrapą. Przykłady w tym artykule były sprawdzone właśnie w ten sposób.
Zadania z kodem i mini-test
Jakie zadania z FastAPI pojawiają się na rozmowach?
Najczęściej to krótkie zadania typu: „napisz endpoint, który zwraca listę z paginacją”, „dodaj walidację do tego modelu”, „zabezpiecz trasę tokenem” albo „znajdź błąd w tym kodzie”. Przy takich zadaniach liczy się nie szybkość pisania, tylko to, czy rozumiesz, dlaczego wybierasz dane rozwiązanie. Mów na głos, jak FastAPI potraktuje Twój kod.
Co jest nie tak w tym kodzie? Mini-test na rozgrzewkę
import time
import requests
from fastapi import FastAPI
app = FastAPI()
@app.post("/register")
async def register(user: dict):
time.sleep(1)
requests.post("https://example.com/hook", json=user)
return user
Są tu co najmniej trzy problemy:
- Blokowanie pętli zdarzeń.
time.sleepirequests.postto wywołania blokujące wewnątrzasync def. Przy dwóch równoległych żądaniach takie endpointy wykonują się po kolei (w teście 1,0 s zamiast 0,5 s). Endpoint powinien być albo zwykłymdef, albo używaćawait asyncio.sleepi asynchronicznego klienta (np.httpx.AsyncClient). - Brak modelu Pydantic. Parametr
user: dictprzyjmuje cokolwiek i nie waliduje niczego. Powinien być modelem z typami i ograniczeniami. - Zwracanie całego wejścia. Odpowiedź odsyła dokładnie to, co przyszło, w tym np. hasło. Potrzebny jest osobny model wyjściowy w
response_model.
Jak przygotować się do rozmowy z FastAPI?
- Zbuduj małe API od zera. Kilka tras, model Pydantic, zależność z
yield, uwierzytelnianie tokenem i testy zTestClient. Zacznij od pierwszego API w FastAPI. - Przetestuj
asyncna własne oczy. Zmierz czas dwóch równoległych żądań dlaasync defz blokującymsleepi dla zwykłegodef. Rozumienie tej różnicy odróżnia kandydatów. - Obejrzyj
/docs. Sprawdź, jak zmiana typu lub dodanieresponse_modelzmienia dokumentację. To najlepszy trening związku między kodem a schematem OpenAPI. - Przećwicz odpowiedzi na głos. Dla każdego pytania z tego artykułu sformułuj w 30 sekund definicję i przykład.
Na czym juniorzy najczęściej tracą punkty?
Nie na trudnych tematach, tylko na trzech drobiazgach, które wychodzą dopiero przy pytaniach uzupełniających:
async defwszędzie. Odpowiedź „async jest szybszy” kończy temat. Wyjaśnij, że blokujące wywołanie wasync defzatrzymuje pętlę zdarzeń, i podaj, kiedy wybrać zwykłydef.- Brak
response_model. Zwracanie obiektów z bazy wprost grozi wyciekiem pól, takich jak hasło. Pokaż, że rozdzielasz modele wejścia i wyjścia. - Mylenie walidacji z autoryzacją. Pydantic sprawdza kształt danych, a nie to, czy użytkownik ma prawo do zasobu. To dwie osobne warstwy.
Jak brzmi dobra odpowiedź, a jak słaba?
Weźmy pytanie o async def i def:
| Odpowiedź | Dlaczego | |
|---|---|---|
| Słaba | „async def jest szybszy, bo jest asynchroniczny.” | Brak mechanizmu. Pytanie uzupełniające („co się stanie, jeśli w środku jest time.sleep?”) od razu to pokaże. |
| Dobra | „async def działa w pętli zdarzeń, więc blokujące wywołanie w środku zatrzymuje wszystkie żądania. Zwykły def FastAPI uruchamia w wątku z puli. Używam async def tylko wtedy, gdy w środku jest await.” | Jest różnica w zachowaniu, konsekwencja i reguła wyboru. |
Ten sam schemat działa przy każdym pytaniu z tego artykułu: definicja, mechanizm, przykład użycia.
Co zrobić, gdy nie znasz odpowiedzi?
Nie zgaduj i nie milcz. Powiedz, co wiesz na pewno, a potem rozumuj na głos, np.: „Nie pamiętam nazwy tego narzędzia do zadań w tle, ale wiem, że szukam sposobu na wykonanie pracy po odpowiedzi, więc sprawdziłbym to w dokumentacji.” Taka odpowiedź pokazuje, że potrafisz sobie poradzić w pracy, a umiejętność znalezienia odpowiedzi jest w codziennej pracy ważniejsza niż pamięć do szczegółów.
Jeśli dopiero planujesz ścieżkę nauki, FastAPI opanujesz najszybciej, budując prawdziwe API razem z bazą danych i testami. W naszym programie robisz to na ścieżce nauki Pythona z mentorem. Ogólny przebieg całego procesu rekrutacji opisujemy w poradniku jak przygotować się do rekrutacji na juniora.


