Why FastAPI?
If you’ve spent any time writing Python APIs, you’ve probably used Flask. Flask is fantastic — minimal, well-known, easy to learn. But around 2018, the Python ecosystem started shifting toward async, type hints became a first-class citizen, and the data validation story was still all over the place. FastAPI showed up and tied all of those threads together into something genuinely modern.
In one framework you get:
- Async support built in (
async defworks everywhere) - Type hints drive request parsing, response serialization, and documentation
- Automatic OpenAPI / Swagger UI generated from your code
- Pydantic for declarative validation with great error messages
- Performance that’s competitive with Node.js and Go
If you’re starting a new Python API project today, FastAPI is the strongest default.
Prerequisites
- Python 3.10+ (FastAPI works on 3.8+, but 3.10+ unlocks nicer typing syntax).
- Comfort with Python functions, classes, and decorators.
- A vague idea of what HTTP requests look like.
Step 1: Install FastAPI
mkdir fastapi_demo && cd fastapi_demo
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
The [standard] extra pulls in uvicorn (the ASGI server) plus a few other useful goodies. If you want a leaner install, use pip install fastapi uvicorn[standard].
Step 2: Hello, FastAPI
Create main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
Run it:
fastapi dev main.py
Visit http://127.0.0.1:8000/ and you’ll see the JSON response. Now go to http://127.0.0.1:8000/docs — that’s a fully interactive Swagger UI generated from your code. No YAML, no decorators describing the schema, no separate spec file. Just type hints.
This is the FastAPI superpower in a nutshell.
Step 3: Path and query parameters
FastAPI reads your function signature to figure out what comes from the URL versus the query string.
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
item_id: int— declared as a path parameter, automatically parsed and validated as an integer. Try/items/abc— you’ll get a clean 422 error.q: str | None = None— has a default, so FastAPI knows it’s an optional query parameter.
Three lines and you have routing, parsing, validation, and docs.
Step 4: Request bodies with Pydantic
For anything more complex than a query string, define a Pydantic model:
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
is_offer: bool = False
@app.post("/items/")
def create_item(item: Item):
return {"created": item.model_dump(), "tax": item.price * 0.1}
Send a POST to /items/ with {"name": "Notebook", "price": 12.5} and you’ll get the parsed payload back along with a computed tax. Send invalid JSON and FastAPI returns a 422 with a list of which fields failed which validators — far more useful than “Bad Request”.
Step 5: Async when you need it
If your handler does I/O (database queries, HTTP calls), make it async:
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/joke")
async def get_joke():
async with httpx.AsyncClient() as client:
response = await client.get("https://icanhazdadjoke.com/", headers={"Accept": "application/json"})
return response.json()
This handler can serve other requests while it waits for the upstream API. Under high load this is the difference between an API that scales and one that doesn’t.
Step 6: Dependency injection
Dependencies in FastAPI are just functions. You declare them in your route signature and FastAPI calls them for you.
from fastapi import Depends, HTTPException, Header
def get_current_user(x_token: str = Header()):
if x_token != "secret-token":
raise HTTPException(status_code=401, detail="Invalid token")
return {"user": "alzy"}
@app.get("/me")
def read_me(user: dict = Depends(get_current_user)):
return user
Dependencies can have their own dependencies, which is how you build clean layers: an auth dependency uses a db dependency, a route uses auth, and the framework wires it all up.
Step 7: Project structure for real apps
A single main.py is great for demos. For real apps, split things up:
fastapi_demo/
├── app/
│ ├── __init__.py
│ ├── main.py # creates the FastAPI app, mounts routers
│ ├── api/
│ │ ├── __init__.py
│ │ ├── deps.py # shared dependencies
│ │ └── routes/
│ │ ├── items.py
│ │ └── users.py
│ ├── core/
│ │ └── config.py # settings via pydantic-settings
│ ├── models/ # Pydantic and ORM models
│ └── services/ # business logic
└── requirements.txt
Mount routers in app/main.py:
from fastapi import FastAPI
from app.api.routes import items, users
app = FastAPI(title="My API")
app.include_router(items.router, prefix="/items", tags=["items"])
app.include_router(users.router, prefix="/users", tags=["users"])
This keeps each router focused and easy to test in isolation.
Step 8: Production deployment
For production, run with multiple workers:
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
Or use Gunicorn as a process manager with Uvicorn workers:
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
Pair that with Nginx as a reverse proxy in front, and you have a production-grade FastAPI deployment.
When not to use FastAPI
To stay honest:
- If you need a full-stack framework with templates, an ORM, an admin, and auth out of the box — use Django.
- If your team has zero async experience and your API is simple, Flask is still fine and the talent pool is bigger.
- If you’re writing a one-off internal script, FastAPI is overkill — write a function.
Conclusion
FastAPI feels like Python’s answer to the question, “what would a modern API framework look like if it were designed in 2020 instead of 2010?” It leans hard into type hints, async, and developer ergonomics, and the result is a framework where the code you write is the documentation, the validator, and the contract.
In future posts we’ll build out a full FastAPI service: connecting to PostgreSQL with SQLAlchemy + asyncpg, adding JWT auth, writing tests with httpx.AsyncClient, and deploying to a real server. Stay tuned, and happy coding!
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .