0% found this document useful (0 votes)
2 views11 pages

Fastapi Complete Guide

The Complete FastAPI Guide provides a comprehensive overview of FastAPI, covering setup, path and query parameters, request bodies, response models, error handling, dependency injection, and database integration with SQLAlchemy. It includes practical examples and code snippets for various features such as authentication, middleware, background tasks, file uploads, and WebSockets. The guide also emphasizes testing, async programming, and deployment strategies for production environments.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views11 pages

Fastapi Complete Guide

The Complete FastAPI Guide provides a comprehensive overview of FastAPI, covering setup, path and query parameters, request bodies, response models, error handling, dependency injection, and database integration with SQLAlchemy. It includes practical examples and code snippets for various features such as authentication, middleware, background tasks, file uploads, and WebSockets. The guide also emphasizes testing, async programming, and deployment strategies for production environments.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

The Complete FastAPI Guide

The Complete FastAPI Guide — Basic to Advanced


Table of Contents
1. Setup & Your First App
2. Path Parameters
3. Query Parameters
4. Request Body & Pydantic Models
5. Response Models & Status Codes
6. Path Operation Configuration
7. Error Handling
8. Dependency Injection
9. Databases with SQLAlchemy
10. Authentication (OAuth2 + JWT)
11. Middleware & CORS
12. Background Tasks
13. File Uploads
14. WebSockets
15. Routers & Project Structure
16. Testing
17. Async Deep Dive
18. Deployment
19. Cheat Sheet

The Complete FastAPI Guide — Basic to Advanced

A hands-on reference for learning FastAPI, with runnable code at every step.

Table of Contents

1. Setup & Your First App


2. Path Parameters
3. Query Parameters
4. Request Body & Pydantic Models
5. Response Models & Status Codes
6. Path Operation Configuration
7. Error Handling
8. Dependency Injection
9. Databases with SQLAlchemy
10. Authentication (OAuth2 + JWT)
11. Middleware & CORS
12. Background Tasks
13. File Uploads
14. WebSockets
15. Routers & Project Structure
16. Testing
17. Async Deep Dive
18. Deployment
19. Cheat Sheet
1. Setup & Your First App

pip install fastapi "uvicorn[standard]"

[Link] :

from fastapi import FastAPI

app = FastAPI()

@[Link]("/")
def read_root():
return {"message": "Hello, FastAPI"}

Run it:

uvicorn main:app --reload

Visit [Link] → your JSON response.


Visit [Link] → auto-generated interactive Swagger UI.
Visit [Link] → alternative docs.

This auto-documentation is FastAPI’s signature feature — it’s generated from your Python type hints, which is why type hints
matter so much throughout this guide.

2. Path Parameters

@[Link]("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}

Because item_id: int is type-hinted, FastAPI: - Converts the string from the URL into an int - Validates it — /items/abc
returns a 422 error automatically - Documents it as an integer in /docs

Enum path parameters (restrict allowed values)

from enum import Enum

class ModelName(str, Enum):


resnet = "resnet"
vgg = "vgg"

@[Link]("/models/{model_name}")
def get_model(model_name: ModelName):
return {"model_name": model_name, "message": "chosen"}

Order matters

Fixed paths must be declared before dynamic ones:

@[Link]("/users/me")
def read_current_user():
return {"user": "current"}

@[Link]("/users/{user_id}")
def read_user(user_id: str):
return {"user_id": user_id}

3. Query Parameters
Anything not in the path becomes a query parameter:

@[Link]("/items/")
def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}

Call as: /items/?skip=5&limit=20

Optional parameters

from typing import Optional

@[Link]("/items/{item_id}")
def read_item(item_id: str, q: Optional[str] = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}

Required query parameter (no default)

@[Link]("/items/{item_id}")
def read_item(item_id: str, needy: str):
return {"item_id": item_id, "needy": needy}

Validation with Query

from fastapi import Query


from typing import Annotated

@[Link]("/items/")
def read_items(
q: Annotated[str | None, Query(min_length=3, max_length=50)] = None
):
return {"q": q}

4. Request Body & Pydantic Models

Define the shape of incoming JSON with a Pydantic model:

from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None

@[Link]("/items/")
def create_item(item: Item):
item_dict = item.model_dump()
if [Link]:
item_dict["price_with_tax"] = [Link] + [Link]
return item_dict

Send a request:

{
"name": "Laptop",
"price": 999.99,
"tax": 50.0
}

FastAPI validates types, generates docs, and returns a 422 with clear error details if the body is malformed.
Combining path, query, and body params

@[Link]("/items/{item_id}")
def update_item(item_id: int, item: Item, q: str | None = None):
result = {"item_id": item_id, **item.model_dump()}
if q:
result["q"] = q
return result

FastAPI figures out from the types which is path, query, or body — no extra syntax needed.

Field validation

from pydantic import BaseModel, Field

class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, description="Must be positive")

Nested models

class Image(BaseModel):
url: str
name: str

class Item(BaseModel):
name: str
price: float
images: list[Image] | None = None

5. Response Models & Status Codes

Control exactly what shape is returned (e.g. to hide a password field):

class UserIn(BaseModel):
username: str
password: str
email: str

class UserOut(BaseModel):
username: str
email: str

@[Link]("/users/", response_model=UserOut)
def create_user(user: UserIn):
return user # password is stripped automatically by response_model

Status codes

from fastapi import status

@[Link]("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
return item

response_model_exclude_unset

Useful for PATCH-style partial updates, so defaults don’t overwrite real data:

@[Link]("/items/{id}", response_model=Item, response_model_exclude_unset=True)


def get_item(id: int):
...
6. Path Operation Configuration

@[Link](
"/items/",
response_model=Item,
summary="Create an item",
description="Create an item with all the information",
tags=["items"],
deprecated=False,
)
def create_item(item: Item):
return item

tags group endpoints in the /docs UI — essential once you have many routes.

7. Error Handling

from fastapi import HTTPException

items = {"foo": "The Foo item"}

@[Link]("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}

Custom exception handlers

from fastapi import Request


from [Link] import JSONResponse

class UnicornException(Exception):
def __init__(self, name: str):
[Link] = name

@app.exception_handler(UnicornException)
def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"{[Link]} caused an error"},
)

8. Dependency Injection

This is FastAPI’s most powerful concept — reusable, composable logic shared across routes.

from fastapi import Depends

def common_params(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@[Link]("/items/")
def list_items(commons: dict = Depends(common_params)):
return commons

@[Link]("/users/")
def list_users(commons: dict = Depends(common_params)):
return commons

Class-based dependencies

class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
[Link] = skip
[Link] = limit

@[Link]("/items/")
def list_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
return commons

Dependencies with yield (setup/teardown, e.g. DB sessions)

def get_db():
db = SessionLocal()
try:
yield db
finally:
[Link]()

@[Link]("/items/")
def list_items(db=Depends(get_db)):
return [Link](Item).all()

Sub-dependencies & global dependencies

app = FastAPI(dependencies=[Depends(verify_token)]) # applies to every route

9. Databases with SQLAlchemy

pip install sqlalchemy

[Link] :

from sqlalchemy import create_engine


from [Link] import sessionmaker, declarative_base

SQLALCHEMY_DATABASE_URL = "sqlite:///./[Link]"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

[Link] :

from sqlalchemy import Column, Integer, String


from database import Base

class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
price = Column(Integer)

[Link] :

from database import engine, SessionLocal, Base


from models import ItemDB

[Link].create_all(bind=engine)

def get_db():
db = SessionLocal()
try:
yield db
finally:
[Link]()

@[Link]("/items/")
def create_item(item: Item, db=Depends(get_db)):
db_item = ItemDB(name=[Link], price=[Link])
[Link](db_item)
[Link]()
[Link](db_item)
return db_item

For async DB access, use sqlalchemy[asyncio] with AsyncSession , or a driver like asyncpg .

10. Authentication (OAuth2 + JWT)

pip install "python-jose[cryptography]" "passlib[bcrypt]"

from datetime import datetime, timedelta


from jose import JWTError, jwt
from [Link] import CryptContext
from [Link] import OAuth2PasswordBearer, OAuth2PasswordRequestForm

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_password(plain, hashed):


return pwd_context.verify(plain, hashed)

def create_access_token(data: dict):


to_encode = [Link]()
expire = [Link]() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return [Link](to_encode, SECRET_KEY, algorithm=ALGORITHM)

@[Link]("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=401, detail="Incorrect username or password")
token = create_access_token({"sub": [Link]})
return {"access_token": token, "token_type": "bearer"}

def get_current_user(token: str = Depends(oauth2_scheme)):


try:
payload = [Link](token, SECRET_KEY, algorithms=[ALGORITHM])
username = [Link]("sub")
if username is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
return username

@[Link]("/users/me")
def read_users_me(current_user: str = Depends(get_current_user)):
return {"username": current_user}

Use /docs — it auto-provides an “Authorize” button once OAuth2PasswordBearer is set up.

11. Middleware & CORS

from [Link] import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=["[Link]
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

Custom middleware

import time
from fastapi import Request

@[Link]("http")
async def add_process_time_header(request: Request, call_next):
start = [Link]()
response = await call_next(request)
[Link]["X-Process-Time"] = str([Link]() - start)
return response

12. Background Tasks

Run code after returning a response (e.g. sending an email):

from fastapi import BackgroundTasks

def write_log(message: str):


with open("[Link]", "a") as f:
[Link](message + "\n")

@[Link]("/send-notification/{email}")
def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_log, f"notification sent to {email}")
return {"message": "Notification sent"}

For heavier async jobs, use Celery, RQ, or Arq instead of BackgroundTasks .

13. File Uploads

from fastapi import UploadFile, File

@[Link]("/upload/")
async def upload_file(file: UploadFile = File(...)):
contents = await [Link]()
return {"filename": [Link], "size": len(contents)}

Multiple files:

@[Link]("/upload-multiple/")
async def upload_multiple(files: list[UploadFile] = File(...)):
return {"filenames": [[Link] for f in files]}

14. WebSockets

from fastapi import WebSocket

@[Link]("/ws")
async def websocket_endpoint(websocket: WebSocket):
await [Link]()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message received: {data}")
15. Routers & Project Structure

For real apps, split routes into separate files using APIRouter .

app/
├── [Link]
├── routers/
│ ├── [Link]
│ └── [Link]
├── [Link]
├── [Link]
└── [Link]

routers/[Link] :

from fastapi import APIRouter, Depends

router = APIRouter(prefix="/items", tags=["items"])

@[Link]("/")
def list_items():
return [{"name": "item1"}]

[Link] :

from fastapi import FastAPI


from routers import items, users

app = FastAPI()
app.include_router([Link])
app.include_router([Link])

16. Testing

pip install pytest httpx

from [Link] import TestClient


from main import app

client = TestClient(app)

def test_read_root():
response = [Link]("/")
assert response.status_code == 200
assert [Link]() == {"message": "Hello, FastAPI"}

def test_create_item():
response = [Link]("/items/", json={"name": "Book", "price": 12.5})
assert response.status_code == 200
assert [Link]()["name"] == "Book"

Run with pytest .

17. Async Deep Dive

Use async def when your route awaits I/O (DB calls, HTTP requests, etc.) using async-compatible libraries:

@[Link]("/data")
async def get_data():
async with [Link]() as client:
response = await [Link]("[Link]
return [Link]()
Rule of thumb: - Use async def + await only with async libraries ( httpx , asyncpg , async SQLAlchemy). - Use plain def if
you’re calling blocking/sync code (like plain requests or blocking DB drivers) — FastAPI runs these in a thread pool automatically
so they don’t block the event loop. - Never call blocking code inside an async def route without await ing it properly — it will
block the entire server.

18. Deployment

Production run (no --reload ):

uvicorn main:app --host [Link] --port 8000 --workers 4

Or with Gunicorn managing Uvicorn workers:

gunicorn main:app -w 4 -k [Link]

Minimal Dockerfile :

FROM python:3.12-slim
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
CMD ["uvicorn", "main:app", "--host", "[Link]", "--port", "8000"]

Put Nginx or a cloud load balancer in front for TLS and reverse proxying.

19. Cheat Sheet

Task Snippet

Path param def f(item_id: int):

Query param def f(q: str = None):

Request body def f(item: PydanticModel):

Response model @[Link]("/x", response_model=Out)

Status code @[Link]("/x", status_code=201)

Raise error raise HTTPException(404, "msg")

Dependency Depends(some_func)

DB session dep def get_db(): yield db

Auth Depends(oauth2_scheme)

Background task background_tasks.add_task(fn)

Router APIRouter(prefix="/x")

Test client TestClient(app)

Suggested learning order

1. Sections 1–5 → build basic CRUD API


2. Section 7–8 → clean error handling + dependencies
3. Section 9–10 → real database + auth
4. Section 15 → refactor into routers
5. Section 16 → add tests
6. Section 17–18 → optimize and ship
Practice tip: build one project (e.g. a to-do API) and add one new concept from this guide to it at a time — auth, then a database,
then tests, then deployment. That’s the fastest way to actually retain this.

You might also like