I switched from Flask to FastAPI for a new service and haven’t looked back. The automatic request validation and OpenAPI docs alone make it worth it. Here’s the stuff I wish I’d known on day one.
Quick start Link to heading
pip install fastapi uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
uvicorn main:app --reload
That item_id: int type hint isn’t just documentation - FastAPI validates it. Hit /items/abc and you get a 422 with a clear error message. No more manual type checking.
Why I prefer it over Flask Link to heading
- Automatic validation - Pydantic models validate request bodies, query params, headers. In Flask I was writing validation code everywhere.
- Auto-generated docs - Swagger at
/docs, ReDoc at/redoc. No config needed. - Async native - Built on Starlette, so async works properly. Flask’s async support always felt bolted on.
- Type hints = documentation - Your IDE knows what’s what, and so do the generated docs.
The learning curve is minimal if you know Flask. The main difference is leaning into type hints and Pydantic.
Request bodies with Pydantic Link to heading
This is where FastAPI shines:
from pydantic import BaseModel, Field
class CreateItem(BaseModel):
name: str = Field(min_length=1, max_length=100)
price: float = Field(gt=0)
description: str | None = None
@app.post("/items/")
def create_item(item: CreateItem):
return item
Invalid requests get rejected before your code even runs. The error messages tell you exactly what’s wrong.
Dependency injection Link to heading
This took me a while to appreciate, but it’s powerful:
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
return db.query(User).filter(User.id == user_id).first()
The Depends() pattern handles setup/teardown cleanly and makes testing easy - just override the dependency.
Gotchas Link to heading
Pydantic v2 migration: If you’re starting fresh, use Pydantic v2. The syntax changed (validator → field_validator, etc.) - see Pydantic v2 migration tips for details. FastAPI 0.100+ supports both but v2 is faster.
Sync vs async: Don’t mix blocking I/O in async routes. If you’re using a sync database driver, use sync route functions. FastAPI handles this correctly but it’s easy to accidentally block the event loop.
Response model: Use response_model to control what gets returned:
@app.get("/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int):
return db_user # password_hash gets stripped out
Further reading Link to heading
- FastAPI docs - genuinely excellent documentation
- Pydantic v2 migration guide - if you’re updating old code
- HN: Django vs FastAPI in 2025 - good discussion on when to use each