Skip to main content

FastAPI Architecture

FastAPI is built on modern Python features and provides an elegant architecture for building high-performance web APIs. Understanding its architecture helps you build more efficient applications and debug issues more effectively.

Introduction

FastAPI's architecture is designed with performance, developer experience, and standards compliance in mind. It combines several powerful components:

  • ASGI foundation: Uses Starlette as its ASGI framework
  • Type annotations: Leverages Python's type hints for validation
  • Dependency injection system: For clean code organization
  • Automatic documentation: OpenAPI and Swagger UI integration

This architecture enables FastAPI to offer both high performance and developer-friendly features without compromise.

ASGI Foundation

What is ASGI?

ASGI (Asynchronous Server Gateway Interface) is the successor to WSGI, allowing Python web applications to handle asynchronous requests.

python
# Traditional WSGI application (synchronous)
def wsgi_app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Hello, World!']

# ASGI application (asynchronous)
async def asgi_app(scope, receive, send):
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')],
})
await send({
'type': 'http.response.body',
'body': b'Hello, World!',
})

FastAPI builds on Starlette, a lightweight ASGI framework, adding features like request validation, serialization, and documentation.

Request Lifecycle

When a request hits a FastAPI application, it goes through several steps:

  1. HTTP Request Arrival: The ASGI server (like Uvicorn) receives the HTTP request
  2. Middleware Processing: Request passes through middleware stack
  3. Route Matching: FastAPI matches the URL path to a route
  4. Dependency Resolution: Dependencies are resolved and injected
  5. Parameter Validation: Request parameters are validated
  6. Handler Execution: Your route function/method executes
  7. Response Generation: Return value is converted to an HTTP response
  8. Middleware (reverse): Response passes back through middleware
  9. HTTP Response: Sent back to the client

Let's visualize this with a simple example:

python
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()

async def verify_token(x_token: str = Header()):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token

@app.get("/items/")
async def read_items(token: str = Depends(verify_token)):
return {"token": token, "message": "Valid access"}

When a request is made to /items/:

  1. FastAPI matches the route to the read_items function
  2. Before executing the function, it resolves the verify_token dependency
  3. The dependency validates the token header
  4. If valid, read_items receives the token value and executes
  5. The return value is automatically converted to JSON

Dependency Injection System

One of FastAPI's most powerful architectural features is its dependency injection system. This allows for:

  • Code reusability
  • Separation of concerns
  • Easier testing
  • Automatic validation

How Dependencies Work

Dependencies in FastAPI are declared using the Depends class:

python
from fastapi import FastAPI, Depends
from typing import Optional

app = FastAPI()

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

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return {"commons": commons}

@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return {"commons": commons}

This architecture allows for:

  • Sub-dependencies: Dependencies can depend on other dependencies
  • Shared logic: Common code can be extracted into dependencies
  • Scoped execution: Dependencies can be executed once per request

Pydantic Models and Validation

FastAPI architecture relies heavily on Pydantic models for data validation, serialization, and documentation.

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
name: str
description: str | None = Field(default=None, max_length=300)
price: float = Field(gt=0)
tax: float | None = None

@app.post("/items/")
async def create_item(item: Item):
return item

When a POST request is made to /items/:

  1. FastAPI reads the request body as JSON
  2. Validates the data against the Item model
  3. Provides helpful errors if validation fails
  4. Makes the validated model available to your function
  5. Includes the model in the API documentation

Middleware Architecture

FastAPI includes a middleware system that allows code execution before and after request processing.

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

app = FastAPI()

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Custom middleware
@app.middleware("http")
async def add_process_time_header(request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response

Middleware follows a nested structure, where each layer can modify the request before passing it to the next layer and modify the response on the way back.

Path Operation Decoration

The routing system in FastAPI is based on path operation decorators that map HTTP methods and paths to functions:

python
from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(description="The ID of the item to get"),
q: str | None = Query(default=None, max_length=50)
):
return {"item_id": item_id, "q": q}

This architecture provides:

  • Clear HTTP method declaration
  • Path parameter validation and conversion
  • Query parameter validation
  • Automatic documentation generation

Real-world Application: Building a Layered Architecture

In larger applications, you can use FastAPI's architecture to create a clean, layered application structure:

my_app/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app initialization
│ ├── dependencies.py # Shared dependencies
│ ├── routers/ # Route modules
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ ├── models/ # Pydantic models
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ ├── crud/ # Database operations
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── services/ # Business logic
│ ├── __init__.py
│ ├── items.py
│ └── users.py

Example implementation:

python
# app/main.py
from fastapi import FastAPI
from .routers import items, users
from .dependencies import get_token_header

app = FastAPI()

app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
python
# app/routers/items.py
from fastapi import APIRouter, Depends
from ..dependencies import get_token_header
from ..services import items as items_service

router = APIRouter()

@router.get("/")
async def read_items(token: str = Depends(get_token_header)):
return await items_service.get_items()

This structure leverages FastAPI's architecture to create maintainable applications by:

  • Separating routing logic from business logic
  • Reusing dependencies across different routes
  • Organizing code by domain/feature

Summary

FastAPI's architecture provides a powerful foundation for building modern web APIs:

  • ASGI foundation enables high performance with async/await
  • Dependency injection promotes clean, testable code
  • Pydantic integration provides robust validation and serialization
  • Middleware system allows cross-cutting concerns
  • Path operation decorators create clear routing

Understanding FastAPI's architecture helps you build better applications that are both performant and maintainable.

Additional Resources

Exercises

  1. Create a FastAPI application with at least three layers: routers, services, and models.
  2. Implement a custom middleware that logs all requests and their processing time.
  3. Create a dependency that validates user permissions and use it in multiple routes.
  4. Build a reusable pagination dependency that can be used across different endpoints.
  5. Implement a layered architecture with proper separation of concerns for a blog API with posts and comments.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)