Overview of FastAPI

FastAPI is a modern Python web framework created by Sebastián Ramírez, designed to build high-performance APIs with minimal code. Its main goals are rapid development, high performance, automatic documentation generation, and strict type checking.


1. Key Features of FastAPI

1.1 High Performance

FastAPI is built on Python’s asynchronous capabilities (asyncio) and uses Starlette as the web core. This allows the framework to achieve performance comparable to Node.js and Go. Key points:

  • Supports both asynchronous (async def) and synchronous (def) endpoints.
  • Can scale via Uvicorn, Gunicorn, or any ASGI server.
  • Compatible with asynchronous clients and databases.

1.2 Typing and Pydantic

FastAPI uses Pydantic for data validation and serialization:

  • Validates input parameters (query, path, body) on the fly.
  • Automatically generates OpenAPI and JSON Schema definitions.
  • Supports type conversions (e.g., str → int, str → datetime).

Example:

from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool | None = None

@app.post("/items/")
async def create_item(item: Item):
    return {"item_name": item.name, "item_price": item.price}

FastAPI automatically ensures that price is a number and name is a string, returning detailed errors if validation fails.


1.3 Automatic Documentation

FastAPI automatically generates documentation:

  • Swagger UI: /docs
  • Redoc: /redoc
  • OpenAPI JSON spec: /openapi.json

This accelerates integration with frontends or external services.


1.4 Dependency Injection (DI)

FastAPI provides a powerful dependency injection system for easy decomposition of application logic:

from fastapi import Depends

def get_db():
    db = "Database connection"
    try:
        yield db
    finally:
        print("Closing DB connection")

@app.get("/users/")
async def read_users(db: str = Depends(get_db)):
    return {"db": db}

DI Advantages:

  • Resource lifecycle management
  • Dependency reuse
  • Easy testing with mocks

2. Internal Architecture of FastAPI

FastAPI is built on two key components: Starlette and Pydantic.

2.1 Starlette — Asynchronous Core

Starlette handles:

  • Routing
  • Middleware and request handling
  • WebSocket support
  • Background tasks
  • TestClient for integration tests
  • Asynchronous events (startup/shutdown)

FastAPI extends Starlette by:

  • Adding Pydantic model support
  • Generating OpenAPI schemas
  • Handling documentation and validation

2.2 Pydantic — Strict Data Validation

Pydantic is used for:

  • JSON validation and serialization
  • Type checking and automatic conversion
  • Generating OpenAPI schemas
  • Handling nested models

Example with nested models:

class Owner(BaseModel):
    name: str
    age: int

class Car(BaseModel):
    model: str
    owner: Owner

car = Car(model="Tesla", owner={"name": "John", "age": "30"})
print(car.owner.age)  # → 30, automatically converted to int

3. Main Components of FastAPI

3.1 Routing

FastAPI supports:

  • HTTP methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
  • Parameters: path, query, body, header, cookie
  • Routes with typed parameters

Example with parameters:

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

3.2 Middleware

Middleware allows global request/response processing:

from starlette.middleware.base import BaseHTTPMiddleware

class SimpleMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        print("Before request")
        response = await call_next(request)
        print("After request")
        return response

app.add_middleware(SimpleMiddleware)

3.3 Background Tasks

FastAPI supports background tasks after responding to the client:

from fastapi import BackgroundTasks

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(message + "\n")

@app.post("/send/")
async def send_email(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "Email sent")
    return {"message": "Request received"}

3.4 Event Handlers

Previously, FastAPI supported application events: startup and shutdown:

@app.on_event("startup")
async def startup_event():
    print("App started")

@app.on_event("shutdown")
async def shutdown_event():
    print("App shutdown")

Now, in modern versions of FastAPI, it is preferable to use lifespan instead of the old @app.on_event("startup") / @app.on_event("shutdown"). Lifespan allows you to combine startup and shutdown logic in a single asynchronous context manager.

from fastapi import FastAPI
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialization code at application startup
    print("App startup — preparing resources")
    yield  # The application handles requests here
    # Shutdown code at application stop
    print("App shutdown — releasing resources")

app = FastAPI(lifespan=lifespan)

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

Explanations:

  • Everything before yield is executed once at application startup.
  • Everything after yield is executed at application shutdown.
  • The old decorators @app.on_event("startup") and @app.on_event("shutdown") are ignored if lifespan is defined.

This approach is recommended for all new FastAPI projects.


3.5 WebSocket Support

from fastapi import WebSocket

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    while True:
        data = await ws.receive_text()
        await ws.send_text(f"Message received: {data}")

3.6 Security and OAuth2

FastAPI integrates with OAuth2 and JWT:

from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
    return {"token": token}

4. Asynchronous Operations and Performance

FastAPI fully supports async/await:

  • Asynchronous routes are non-blocking
  • Ideal for async databases (asyncpg, Tortoise ORM) or HTTP clients (httpx)
  • Compatible with multithreaded or multiprocess servers

Async HTTP example:

import httpx

@app.get("/proxy")
async def proxy():
    async with httpx.AsyncClient() as client:
        r = await client.get("https://example.com")
        return {"status": r.status_code, "content": r.text[:50]}

5. Testing FastAPI Applications

FastAPI works seamlessly with pytest and provides TestClient for synchronous testing:

from fastapi.testclient import TestClient

client = TestClient(app)

def test_read_item():
    response = client.get("/items/42")
    assert response.status_code == 200
    assert response.json() == {"item_id": 42, "q": None}

6. Additional Features

  • GZip Middleware for response compression
  • Dependency overrides for testing or service replacement

7. Database Integration

FastAPI does not enforce any ORM:

  • SQLAlchemy (sync & async)
  • Tortoise ORM (async)
  • MongoDB via motor (async)
  • Redis via aioredis

Async SQLAlchemy example:

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite+aiosqlite:///./test.db"

engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def get_db():
    async with async_session() as session:
        yield session

8. Advanced Features and Internal Architecture

8.1 Request Flow

HTTP Request → ASGI server (Uvicorn) → Starlette → FastAPI router → DI → Endpoint → Response → Client
  • Starlette converts HTTP requests to ASGI events
  • FastAPI handles DI and Pydantic validation
  • Endpoint returns a response

8.2 Global Dependencies

app = FastAPI(dependencies=[Depends(verify_token)])

All endpoints automatically go through authentication.


8.3 Exception Handlers

FastAPI allows you to create custom error handlers for individual exceptions or globally:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    return JSONResponse(
        status_code=400,
        content={"error": "Invalid value", "details": str(exc)},
    )

Centralizes error handling and returns clear API responses.


8.4 Response Models

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

@app.get("/users/{user_id}", response_model=UserOut)
async def read_user(user_id: int):
    return {"username": "fastapi", "email": "fastapi@example.com"}

8.5 Streaming Responses

Supports StreamingResponse for large files or server-sent events:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

async def iterfile():
    for i in range(10):
        yield f"line {i}\n"
        await asyncio.sleep(0.5)

@app.get("/stream")
async def stream():
    return StreamingResponse(iterfile(), media_type="text/plain")

Used for streaming logs, large files or SSE (Server-Sent Events).


8.6 File Upload and Form Data

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/upload/")
async def upload(file: UploadFile = File(...)):
    content = await file.read()
    return {"filename": file.filename, "size": len(content)}
  • Efficient for large files
  • Does not load everything into memory at once

8.7 CORS Middleware

FastAPI allows you to configure CORS via built-in middleware to allow cross-domain requests:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Used for frontend-backend communication across different domains.


8.8 Sub-Applications and Mounts

FastAPI allows you to mount sub-applications within the main application. This is convenient for admin panels, API versioning, or integrating third-party services.

from fastapi import FastAPI

app = FastAPI()
admin_app = FastAPI()

@admin_app.get("/dashboard")
async def dashboard():
    return {"admin": True}

app.mount("/admin", admin_app)  # admin_app routes accessible at /admin

Features:

  • A sub-application can have its own middleware and dependencies.
  • The main application continues to operate independently.
  • Used for code isolation and API versioning.

9. Background Tasks and Message Brokers

FastAPI integrates easily with Celery, Redis Queue, or Dramatiq:

@celery.task
def send_email(email):
    print(f"Sent to {email}")

10. FastAPI + GraphQL

graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

11. Asynchronous Serialization and Custom Responses

@app.get("/json", response_class=ORJSONResponse)
@app.get("/html", response_class=HTMLResponse)

Conclusion

FastAPI is more than just a REST API framework. It is a platform for building complex asynchronous systems:

  • Handles millions of requests with minimal latency
  • Integrates seamlessly with microservices and external APIs
  • Supports type-safe, readable, and declarative code
  • Provides automatic documentation, testability, and extensibility

FastAPI combines modern Python features with high-performance design, making it ideal for both simple APIs and large-scale, production-grade systems.