FastAPI: modern Python APIs
Lesson 4 of 6
4FastAPI: modern Python APIs
The big pictureIn Flask you check every field by hand and write docs separately. FastAPI reads the type hints on your function and infers both, and it is today's favourite for AI services.
What the picture shows: Before data reaches your function it is validated and converted to the right types; otherwise FastAPI replies 422, explaining each wrong field.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
Imagine a paper booking form with fixed boxes: "number: 1 to 10". A clerk rejects any wrong form before it reaches the manager. In FastAPI you design the form (Pydantic), the framework is the clerk, and your function is the manager who only ever sees valid forms.
FastAPI is built on Starlette (ASGI) and Pydantic v2. From the function signature it infers parameter sources (path, query, body), validates and converts types, and generates an OpenAPI schema shown at /docs (Swagger UI). Depends is a composable dependency injection system (a database session, the current user) that tests swap with dependency_overrides. async def functions do non-blocking I/O, while plain def ones run in a thread pool.
Before You Start
🐍Python and classes
You know functions, classes and simple type hints.
🌐REST
The REST lesson with Flask in the APIs track.
The Core Idea
1First route and free docs
A plain function with an @app.get decorator. The item_id: int path parameter becomes a number and text is rejected automatically. Open /docs to see and try every route in the browser.
from fastapi import FastAPI
app = FastAPI(title="Cafe API")
@app.get("/drinks/{item_id}")
def read_drink(item_id: int, size: str = "medium"):
return {"id": item_id, "size": size}
# run with: pip install "fastapi[standard]" then fastapi dev main.py2Pydantic: data with a contract
A Pydantic model is a class with types and constraints. It converts, validates and gives detailed errors. This example runs with no server so you see validation alone (it needs pydantic installed).
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class Order(BaseModel):
item: Literal["latte", "mocha", "v60"]
cups: int = Field(ge=1, le=10)
note: str | None = Field(default=None, max_length=80)
print(Order(item="latte", cups="2")) # "2" becomes 2
try:
Order(item="tea", cups=0)
except ValidationError as e:
for err in e.errors():
print(err["loc"][0], "->", err["msg"])3Depends: swappable dependencies
What many functions need (the current user, a database session) you write once as a function and ask for with Depends. In tests you swap it for a fake.
from fastapi import Depends, Header, HTTPException
def current_user(authorization: str = Header()):
if authorization != "Bearer demo-token":
raise HTTPException(status_code=401, detail="log in first")
return {"name": "Sara"}
@app.get("/me")
def me(user: dict = Depends(current_user)):
return userasync def for a route that calls a slow synchronous library (like requests or a sync database driver). The event loop stalls for every request. Either use an async library (httpx) or make the function a plain def.4Flask, FastAPI or Express?
Flask is simple and flexible, good for learning and small sites. FastAPI when you want types, validation and automatic docs, especially beside Python AI models. Express when your team writes JavaScript on the front end and wants one language.
Worked example
The orders API in FastAPI with a test. Notice there is no hand-written validation, and the 422 error names the field.
from typing import Literal
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
app = FastAPI()
class NewOrder(BaseModel):
item: Literal["latte", "mocha", "v60"]
cups: int = Field(ge=1, le=10)
class Order(NewOrder):
id: int
ORDERS: list[Order] = []
@app.post("/orders", status_code=201, response_model=Order)
def create(order: NewOrder):
saved = Order(id=len(ORDERS) + 1, **order.model_dump())
ORDERS.append(saved)
return saved
@app.get("/orders/{order_id}", response_model=Order)
def get(order_id: int):
for o in ORDERS:
if o.id == order_id:
return o
raise HTTPException(404, "no such order")
client = TestClient(app)
assert client.post("/orders", json={"item": "latte", "cups": 2}).status_code == 201
bad = client.post("/orders", json={"item": "tea", "cups": 0})
assert bad.status_code == 422
print([e["loc"][-1] for e in bad.json()["detail"]]) # ['item', 'cups']Myth vs Reality
| Myth | Reality |
|---|---|
| FastAPI is faster than everything, so always use it. | It is fast for Python, but the database and network are usually the bottleneck, not the framework. |
| Type hints in Python are just decoration. | In FastAPI and Pydantic they are what validation, conversion and docs run on. |
Test your understanding
You sent cups: 0 and the model says ge=1. What is the reply?
FastAPI replies 422 Unprocessable Entity with details for each wrong field.
Where do you see your API's interactive docs?
FastAPI generates an OpenAPI schema and shows it at /docs automatically.
What is Depends for?
Dependency injection removes repetition and makes testing easy.
Quick review cards
Check Your Understanding
Why not write manual validation in FastAPI?
Because the Pydantic model describes the constraints and the framework applies them before data reaches your function.
When async def and when def?
async def with async libraries (httpx, asyncpg); plain def with synchronous ones, which FastAPI runs in threads.
How do you test a protected route without a real login?
app.dependency_overrides[current_user] = lambda: {"name": "test"}.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Done with this lesson?
Sign in to save your progress.