📘 Complete Guide — Basic to Advanced

FastAPI Tutorial

Build high-performance Python APIs from scratch. Every import explained, every concept covered, every pattern production-ready.

28
Sections
100+
Code Examples
50+
Imports Explained

Table of Contents

01

What is FastAPI?

FastAPI is a modern, high-performance Python web framework for building APIs. It's built on two core technologies: Starlette (for the async web layer) and Pydantic (for data validation). It's one of the fastest Python frameworks available — comparable to Node.js and Go in benchmarks.

⚡ Key Features

Automatic Swagger docs • Type hints-based validation • Async support built-in • Dependency injection system • OAuth2 & JWT support • Generates OpenAPI schema automatically

02

Installation

bash
pip install fastapi
pip install uvicorn[standard]
PackageWhat It Does
fastapi The framework itself — provides routing, validation, docs, dependency injection
uvicorn An ASGI server that runs your FastAPI app. ASGI (Asynchronous Server Gateway Interface) is the async successor to WSGI. The [standard] extra installs performance deps like uvloop and httptools
03

Your First FastAPI App

python main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, World!"}

Import Breakdown

ImportExplanation
FastAPI The main class of the framework. app = FastAPI() creates an ASGI application instance. This class inherits from Starlette's Starlette class and adds validation, documentation, and dependency injection on top.

Run the App

bash
uvicorn main:app --reload
PartMeaning
mainYour Python filename (main.py)
appThe FastAPI instance variable
--reloadAuto-restart on code changes (development only)

What Happens Internally

1
Uvicorn starts an ASGI server on port 8000
2
FastAPI registers the route / with method GET
3
When a request hits /, FastAPI calls read_root()
4
The returned dict is automatically serialized to JSON
📄 Auto-Generated Docs

Swagger UI: http://127.0.0.1:8000/docs
ReDoc: http://127.0.0.1:8000/redoc
OpenAPI JSON: http://127.0.0.1:8000/openapi.json

04

Path Parameters

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

{item_id} in the path becomes a function parameter. The type hint int tells FastAPI to validate and convert the path parameter. If someone sends /items/abc, FastAPI returns a 422 Validation Error automatically — powered by Pydantic under the hood.

⚠️ Route Order Matters

If you have both /users/me and /users/{user_id}, put /users/me FIRST. Otherwise /users/me would match /users/{user_id} with user_id="me".

Using Enum for Fixed Values

python
from enum import Enum
from fastapi import FastAPI

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"

app = FastAPI()

@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
    return {"model": model_name.value}
ImportExplanation
Enum from enum Python's built-in enumeration. Inheriting from both str and Enum makes values treated as strings in the OpenAPI schema — shows as a dropdown in Swagger UI.
05

Query Parameters

python
from typing import Union
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

Any function parameter NOT in the path is automatically a query parameter. URL example: /items/5?q=hello

ImportExplanation
Union from typing Declares that q can be str OR None. Setting default to None makes it optional. In Python 3.10+ you can use str | None instead.
06

Request Body with Pydantic

python
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Union

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None

@app.post("/items/")
def create_item(item: Item):
    item_dict = item.model_dump()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict
ImportExplanation
BaseModel from pydantic The foundation of Pydantic's data validation. Every field with type hints becomes a validated field. Pydantic parses incoming JSON, validates types, coerces where possible ("25.5"25.5), and raises clear errors when validation fails.

What Happens When a Request Comes In

1
FastAPI reads the request body as JSON
2
Pydantic validates each field against type hints
3
If name is missing → 422 error
4
If price is "25.5" → Pydantic coerces it to 25.5
5
If price is "hello"422 error with details
6
The validated Item instance is passed to your function
💡 Key Pydantic v2 Methods

item.model_dump() — Converts model → dict
item.model_dump_json() — Converts → JSON string
Item.model_validate(data) — Creates instance from dict

07

Field Validation

python
from pydantic import BaseModel, Field
from fastapi import FastAPI, Query, Path

app = FastAPI()

class Item(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    price: float = Field(..., gt=0, description="Must be > zero")
    tax: float = Field(default=0.0, ge=0, le=100)

@app.get("/items/")
def read_items(
    q: str = Query(default=None, min_length=3, max_length=50),
    item_id: int = Path(..., ge=1, le=1000),
):
    return {"q": q, "item_id": item_id}
ImportExplanation
Field from pydantic Extra validation for model fields. ... (Ellipsis) = required. Params: gt, ge, lt, le for numbers; min_length, max_length, pattern for strings.
Query from fastapi Validation for query parameters. Works like Field but for URL params like ?q=hello. Also supports List[str] for multi-value params.
Path from fastapi Validation for path parameters. Since path params are always required (part of the URL), ... is standard.
08

Response Models

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

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

@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
    return user  # password is automatically filtered out!
🔒 Security Feature

response_model=UserOut filters the response through UserOut. Fields NOT in UserOut (like password) are stripped. You never accidentally leak sensitive data.

09

Status Codes

python
from fastapi import FastAPI, status

app = FastAPI()

@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(name: str):
    return {"name": name}
ImportExplanation
status from fastapi HTTP status code constants. Using status.HTTP_201_CREATED instead of 201 → readable code + IDE autocomplete. Common: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 422 Validation Error
10

Form Data & File Uploads

python
from fastapi import FastAPI, File, UploadFile, Form

app = FastAPI()

@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

@app.post("/upload/")
async def upload(file: UploadFile = File(...)):
    contents = await file.read()
    return {"filename": file.filename, "size": len(contents)}
ImportExplanation
FormReads from form data (application/x-www-form-urlencoded) instead of JSON. Required for HTML form submissions.
FileDeclares a parameter as file upload. File content received as bytes.
UploadFileWraps uploaded file with attributes: .filename, .content_type, await .read(), await .seek(0). Small files in memory, large files on disk. Requires pip install python-multipart.
11

Error Handling

python
from fastapi import FastAPI, HTTPException, status

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id == 0:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Item not found",
            headers={"X-Error": "custom header"},
        )
    return {"item_id": item_id}
ImportExplanation
HTTPException The standard way to return HTTP errors. When raised, FastAPI stops execution and returns the status code + detail as JSON. You can also add custom headers.
12

Dependency Injection

This is one of FastAPI's most powerful features.

python
from fastapi import FastAPI, Depends

app = FastAPI()

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

@app.get("/items/")
def read_items(commons: dict = Depends(common_parameters)):
    return commons
ImportExplanation
Depends from fastapi The core of dependency injection. When FastAPI sees Depends(func), it: (1) Calls func with appropriate request parameters, (2) Passes the return value to your endpoint. Dependencies can have sub-dependencies → chains.

Dependency with yield (DB Sessions)

python
def get_db():
    db = SessionLocal()
    try:
        yield db        # Give session to endpoint
    finally:
        db.close()      # ALWAYS close, even on errors

@app.get("/items/")
def get_items(db: Session = Depends(get_db)):
    return db.query(Item).all()

Code before yield runs before the endpoint. Code after yield (in finally) runs after the response is sent — perfect for DB session cleanup.

13

OAuth2 + JWT Authentication

python
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    # ... validate and return user
ImportExplanation
OAuth2PasswordBearerExtracts Bearer token from Authorization header. tokenUrl tells Swagger UI where to send login credentials. Adds 🔒 icon to endpoints.
OAuth2PasswordRequestFormParses standard OAuth2 login form (username + password as form data). Powers Swagger UI's "Authorize" button.
jwt from joseCreates & decodes JSON Web Tokens. jwt.encode() creates, jwt.decode() verifies. Install: pip install python-jose[cryptography]
CryptContext from passlibPassword hashing with bcrypt. Intentionally slow (~100ms) to prevent brute force. Install: pip install passlib[bcrypt]
14

Middleware

python
import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.middleware("http")
async def add_timing(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    response.headers["X-Time"] = str(time.time() - start)
    return response
ImportExplanation
CORSMiddlewareHandles Cross-Origin Resource Sharing. Without this, your frontend on localhost:3000 can't call your API on localhost:8000 — browsers block cross-origin requests by default.
call_nextPasses the request to the next middleware or endpoint. Request → Middleware (before) → Endpoint → Middleware (after) → Response.
15

Background Tasks

python
from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def send_email(email: str, message: str):
    # Runs AFTER response is sent
    print(f"Sent to {email}: {message}")

@app.post("/notify/{email}")
async def notify(email: str, bg: BackgroundTasks):
    bg.add_task(send_email, email, "Welcome!")
    return {"message": "Notification queued"}
ImportExplanation
BackgroundTasksCollects tasks to run after the response is sent. Client doesn't wait. Ideal for emails, logging, analytics. For heavy work, use Celery instead.
16

Routers (Project Structure)

python routers/items.py
from fastapi import APIRouter

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

@router.get("/")
def read_items():
    return [{"name": "Item1"}]
python main.py
from fastapi import FastAPI
from routers import items

app = FastAPI()
app.include_router(items.router)
ImportExplanation
APIRouterWorks like FastAPI() for defining routes but designed to be included into the main app. Like Django's per-app urls.py or Flask's Blueprint. Key params: prefix, tags, dependencies.
17

Database with SQLAlchemy

python
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from pydantic import BaseModel, ConfigDict
from fastapi import Depends

engine = create_engine("sqlite:///./app.db")
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

# ORM Model (database table)
class ItemDB(Base):
    __tablename__ = "items"
    id = Column(Integer, primary_key=True)
    name = Column(String)

# Pydantic Schema (API validation)
class ItemResponse(BaseModel):
    id: int
    name: str
    model_config = ConfigDict(from_attributes=True)
ImportExplanation
create_engineCreates DB connection engine with connection pool. URL format: sqlite:///./app.db or postgresql://user:pass@host/db
sessionmakerFactory for Session objects. A Session tracks changes and writes on commit().
declarative_baseBase class for ORM models. Inheriting from Base maps class → table.
ConfigDict(from_attributes=True)Tells Pydantic to read ORM attributes (item.name) instead of dict keys. Required when returning SQLAlchemy objects.
18

Async/Await

python
# Async — use when calling await-compatible libraries
@app.get("/async")
async def async_endpoint():
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://api.example.com")
    return resp.json()

# Sync — use with blocking I/O (runs in thread pool automatically)
@app.get("/sync")
def sync_endpoint():
    time.sleep(1)  # Won't freeze the server
    return {"done": True}
📌 Rule of Thumb

async def — when using await (httpx, aiofiles, async DB drivers).
def (no async) — when using synchronous libs (requests, standard file I/O). FastAPI runs these in a thread pool automatically.

19

WebSockets

python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    try:
        while True:
            data = await ws.receive_text()
            await ws.send_text(f"Echo: {data}")
    except WebSocketDisconnect:
        print("Client disconnected")
ImportExplanation
WebSocketRepresents a WebSocket connection. Methods: accept(), send_text(), send_json(), receive_text(), close().
WebSocketDisconnectException raised when client disconnects. Catch it to handle cleanup gracefully.
20

Complete Import Reference

python All FastAPI imports in one place
# ═══ CORE ═══
from fastapi import FastAPI          # Main application class
from fastapi import APIRouter        # Sub-router for organizing routes
from fastapi import Depends          # Dependency injection
from fastapi import HTTPException    # Raise HTTP errors
from fastapi import Request          # Access raw request object
from fastapi import status           # HTTP status code constants

# ═══ PARAMETERS ═══
from fastapi import Query            # Query parameter validation
from fastapi import Path             # Path parameter validation
from fastapi import Body             # Request body validation
from fastapi import Header           # Header extraction
from fastapi import Cookie           # Cookie extraction
from fastapi import Form             # Form data extraction
from fastapi import File             # File upload (bytes)
from fastapi import UploadFile       # File upload (with metadata)

# ═══ RESPONSES ═══
from fastapi.responses import JSONResponse
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi.responses import StreamingResponse
from fastapi.responses import FileResponse

# ═══ SECURITY ═══
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordRequestForm

# ═══ MIDDLEWARE ═══
from fastapi.middleware.cors import CORSMiddleware

# ═══ BACKGROUND & WEBSOCKET ═══
from fastapi import BackgroundTasks
from fastapi import WebSocket, WebSocketDisconnect

# ═══ TESTING ═══
from fastapi.testclient import TestClient

# ═══ PYDANTIC ═══
from pydantic import BaseModel       # Base for data models
from pydantic import Field           # Field-level validation
from pydantic import ConfigDict      # Model configuration
from pydantic import field_validator # Custom field validators
from pydantic import model_validator # Model-level validators

# ═══ PYTHON TYPING ═══
from typing import Union             # str | None
from typing import List, Dict, Optional
from typing import Annotated         # Modern Query/Path pattern