Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
28 minLesson 22 of 34
Python for Web & APIs

Building REST APIs with FastAPI

Building REST APIs with FastAPI

FastAPI is the fastest way to build production-ready Python APIs. It's built on async Python, uses Python type hints for automatic validation, and generates interactive documentation automatically. It's faster than Flask and Django REST Framework in benchmarks.

Your First FastAPI App

# pip install fastapi uvicorn[standard] pydantic
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
from typing import Optional, List
import uvicorn

app = FastAPI(
    title="My API",
    description="A sample FastAPI application",
    version="1.0.0"
)

# GET endpoint
@app.get("/")
async def root():
    return {"message": "Hello, World!"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

# Run the server
if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

# Access at: http://localhost:8000
# Interactive docs: http://localhost:8000/docs
# Alternative docs: http://localhost:8000/redoc

Path Parameters and Query Parameters

@app.get("/users/{user_id}")
async def get_user(user_id: int):  # Type hint validates and converts
    # FastAPI returns 422 if user_id isn't a valid integer
    return {"user_id": user_id}

@app.get("/items/{item_id}")
async def get_item(
    item_id: int,
    q: Optional[str] = None,          # Optional query param
    limit: int = 10,                   # Default value
    offset: int = 0
):
    result = {"item_id": item_id, "limit": limit, "offset": offset}
    if q:
        result["query"] = q
    return result

Request Body with Pydantic Models

from pydantic import BaseModel, Field, EmailStr, validator
from typing import Optional
from datetime import datetime

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50, pattern="^[a-zA-Z0-9_]+$")
    email: EmailStr  # pip install email-validator
    full_name: str = Field(..., min_length=2)
    age: Optional[int] = Field(None, ge=0, le=150)
    
    @validator('username')
    def username_lowercase(cls, v):
        return v.lower()

class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    full_name: str
    created_at: datetime
    
    class Config:
        from_attributes = True  # Allow creating from ORM objects

# In-memory database (in production, use a real DB)
fake_db: dict[int, dict] = {}
next_id = 1

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    global next_id
    new_user = {
        "id": next_id,
        **user.model_dump(),
        "created_at": datetime.utcnow()
    }
    fake_db[next_id] = new_user
    next_id += 1
    return new_user

@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    if user_id not in fake_db:
        raise HTTPException(status_code=404, detail=f"User {user_id} not found")
    return fake_db[user_id]

@app.get("/users", response_model=List[UserResponse])
async def list_users(skip: int = 0, limit: int = 10):
    users = list(fake_db.values())
    return users[skip : skip + limit]

@app.delete("/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
    if user_id not in fake_db:
        raise HTTPException(status_code=404, detail="User not found")
    del fake_db[user_id]

Dependency Injection

from fastapi import Depends
from functools import lru_cache

# Configuration dependency
class Settings:
    app_name: str = "My App"
    database_url: str = "sqlite:///./app.db"
    api_key: str = "secret"

@lru_cache
def get_settings() -> Settings:
    return Settings()

# Authentication dependency
async def get_current_user(
    token: str,
    settings: Settings = Depends(get_settings)
):
    if token != settings.api_key:
        raise HTTPException(
            status_code=401,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"}
        )
    return {"username": "alice"}

# Use dependencies in endpoints
@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
    return {"message": f"Hello, {current_user['username']}!"}

Background Tasks

from fastapi import BackgroundTasks
import smtplib
from email.message import EmailMessage

def send_welcome_email(email: str, username: str):
    """Runs in background — doesn't block the response."""
    # Simulate sending email
    print(f"Sending welcome email to {email}...")
    import time
    time.sleep(2)
    print(f"Email sent to {email}")

@app.post("/register")
async def register(user: UserCreate, background_tasks: BackgroundTasks):
    # Create user immediately
    new_user = create_user_in_db(user)
    
    # Send email in background — response returns immediately
    background_tasks.add_task(
        send_welcome_email, 
        email=user.email, 
        username=user.username
    )
    
    return {"message": "User created", "user_id": new_user["id"]}

Middleware and CORS

from fastapi.middleware.cors import CORSMiddleware
import time
import logging

logger = logging.getLogger(__name__)

# CORS — allow frontend to call your API
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "https://yourdomain.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Request logging middleware
@app.middleware("http")
async def log_requests(request, call_next):
    start = time.time()
    response = await call_next(request)
    duration = time.time() - start
    
    logger.info(
        f"{request.method} {request.url.path} "
        f"→ {response.status_code} in {duration:.3f}s"
    )
    return response

Error Handling

from fastapi import Request
from fastapi.responses import JSONResponse

class DatabaseError(Exception):
    pass

@app.exception_handler(DatabaseError)
async def database_error_handler(request: Request, exc: DatabaseError):
    return JSONResponse(
        status_code=503,
        content={"detail": "Database unavailable", "error": str(exc)}
    )

@app.exception_handler(404)
async def not_found_handler(request: Request, exc):
    return JSONResponse(
        status_code=404,
        content={"detail": f"Route {request.url.path} not found"}
    )

Complete Routers for Large Apps

# routers/users.py
from fastapi import APIRouter

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

@router.get("/")
async def list_users():
    return []

@router.post("/")
async def create_user():
    return {}

# main.py
from routers import users, products, orders

app.include_router(users.router)
app.include_router(products.router, prefix="/v1")
app.include_router(orders.router)

Run your API: uvicorn main:app --reload — FastAPI's auto-generated /docs page gives you an interactive playground to test every endpoint instantly.

Next lesson: Web Scraping with BeautifulSoup — extracting data from websites.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!