Neural Architecture Hub
Python

FastAPI

FastAPI endpoints, validation, dependencies, security, testing, and deployment reference.

FastAPI Developer Guide

FastAPI is a Python framework for building APIs with standard type hints. Typed route parameters and Pydantic models provide request validation, serialization, and generated OpenAPI documentation.

Getting Started

Installation

Create a virtual environment and install FastAPI with its standard development server dependencies:

python -m venv .venv

# macOS/Linux
source .venv/bin/activate

# PowerShell
.\.venv\Scripts\Activate.ps1

pip install "fastapi[standard]"

First Application

Create main.py:

from fastapi import FastAPI

app = FastAPI(title="Store API")

@app.get("/")
async def root() -> dict[str, str]:
    return {"message": "Hello, FastAPI"}

Run the local development server:

fastapi dev main.py

Useful local URLs:

http://127.0.0.1:8000/          # API route
http://127.0.0.1:8000/docs      # Swagger UI
http://127.0.0.1:8000/redoc     # ReDoc documentation
http://127.0.0.1:8000/openapi.json

Path Operations

Common HTTP Methods

from fastapi import FastAPI, status

app = FastAPI()


@app.get("/items")
async def list_items():
    return []


@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item():
    return {"created": True}


@app.put("/items/{item_id}")
async def replace_item(item_id: int):
    return {"id": item_id}


@app.patch("/items/{item_id}")
async def update_item(item_id: int):
    return {"id": item_id}


@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    return None

Path and Query Parameters

Parameters named in the route path become path parameters. Other simple typed parameters become query parameters:

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def get_item(
    item_id: int,
    search: str | None = None,
    limit: int = Query(default=20, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
):
    return {
        "id": item_id,
        "search": search,
        "limit": limit,
        "offset": offset,
    }

FastAPI rejects values that do not satisfy the declared types or constraints and documents the accepted inputs in OpenAPI.

Request Bodies and Responses

Pydantic Models

Declare JSON request bodies with BaseModel:

from pydantic import BaseModel, Field


class ItemCreate(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0)
    description: str | None = None
    in_stock: bool = True
from fastapi import FastAPI, status

app = FastAPI()


@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemCreate):
    return item

Response Models

Keep input and public output models separate so internal fields are not exposed:

from pydantic import BaseModel


class UserCreate(BaseModel):
    email: str
    password: str


class UserPublic(BaseModel):
    id: int
    email: str


@app.post("/users", response_model=UserPublic, status_code=201)
async def create_user(user: UserCreate):
    return {"id": 1, "email": user.email, "password": "not-returned"}

The response_model filters and validates data returned to the client.

Status Codes and Errors

from fastapi import HTTPException, status

items = {1: {"id": 1, "name": "Keyboard"}}


@app.get("/items/{item_id}")
async def get_item(item_id: int):
    item = items.get(item_id)
    if item is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Item not found",
        )
    return item

Headers, Cookies, and Files

from typing import Annotated

from fastapi import Cookie, File, Header, UploadFile


@app.get("/request-info")
async def request_info(
    user_agent: Annotated[str | None, Header()] = None,
    session_id: Annotated[str | None, Cookie()] = None,
):
    return {"user_agent": user_agent, "session_id": session_id}


@app.post("/uploads")
async def upload(file: UploadFile = File(...)):
    content = await file.read()
    return {"filename": file.filename, "size": len(content)}

Install multipart support when accepting forms or uploaded files:

pip install python-multipart

Organizing an Application

Suggested Layout

app/
  __init__.py
  main.py
  dependencies.py
  models.py
  schemas.py
  routers/
    __init__.py
    items.py
tests/
  test_items.py

Use an API Router

# app/routers/items.py
from fastapi import APIRouter, HTTPException

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


@router.get("/{item_id}")
async def get_item(item_id: int):
    if item_id < 1:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"id": item_id}
# app/main.py
from fastapi import FastAPI

from .routers import items

app = FastAPI(title="Store API", version="1.0.0")
app.include_router(items.router)
fastapi dev app/main.py

Dependencies

Shared Request Logic

Dependencies are reusable callables for authorization, pagination, service objects, and database sessions:

from typing import Annotated

from fastapi import Depends, Query


def pagination(
    limit: int = Query(default=20, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
) -> dict[str, int]:
    return {"limit": limit, "offset": offset}


@app.get("/items")
async def list_items(page: Annotated[dict[str, int], Depends(pagination)]):
    return page

Dependencies With Cleanup

Use yield when a resource must be closed after a request:

from collections.abc import Generator


def get_db() -> Generator[str, None, None]:
    connection = "database-connection"
    try:
        yield connection
    finally:
        pass  # Close a real database session here.
from typing import Annotated

from fastapi import Depends


@app.get("/reports")
async def reports(db: Annotated[str, Depends(get_db)]):
    return {"connection": db}

Database Pattern With SQLAlchemy

Session Dependency

from collections.abc import Generator

from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

DATABASE_URL = "sqlite:///./app.db"

engine = create_engine(
    DATABASE_URL,
    connect_args={"check_same_thread": False},
)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)


def get_db() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

CRUD Endpoint Pattern

from typing import Annotated

from fastapi import Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session


class ItemPublic(BaseModel):
    id: int
    name: str


@app.get("/items/{item_id}", response_model=ItemPublic)
def read_item(item_id: int, db: Annotated[Session, Depends(get_db)]):
    item = db.get(Item, item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Item not found")
    return item

Keep database table models separate from request and response schema models. Apply schema changes through migrations rather than recreating tables in production.

Authentication and Authorization

Bearer Token Dependency

from typing import Annotated

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


async def current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    if token != "example-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"username": "demo"}


@app.get("/users/me")
async def read_me(user: Annotated[dict, Depends(current_user)]):
    return user

This demonstrates dependency wiring only. Real applications should hash passwords, validate signed or stored tokens, set expiration rules, and load users from a trusted store.

Router-Level Protection

from fastapi import APIRouter, Depends

admin_router = APIRouter(
    prefix="/admin",
    dependencies=[Depends(current_user)],
)


@admin_router.get("/stats")
async def stats():
    return {"active_users": 10}

Middleware and CORS

Allow browser clients only from intended origins:

from fastapi.middleware.cors import CORSMiddleware

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

Avoid allow_origins=["*"] when credentials are allowed or when the API should not be publicly callable from arbitrary browser origins.

Background Tasks and Lifespan

Small After-Response Tasks

from fastapi import BackgroundTasks


def write_notification(email: str) -> None:
    print(f"Notify {email}")


@app.post("/notifications")
async def notify(email: str, tasks: BackgroundTasks):
    tasks.add_task(write_notification, email)
    return {"message": "Notification scheduled"}

Use BackgroundTasks for short work in the same process. For durable or CPU-heavy jobs, use a job queue so work can retry and continue if the API process restarts.

Application Startup and Shutdown

from contextlib import asynccontextmanager

from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.ready = True
    yield
    app.state.ready = False


app = FastAPI(lifespan=lifespan)

Testing

Test an Endpoint

Install testing dependencies:

pip install pytest httpx
# tests/test_main.py
from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


def test_root():
    response = client.get("/")

    assert response.status_code == 200
    assert response.json() == {"message": "Hello, FastAPI"}
pytest
pytest -q

Override Dependencies

Replace authentication or databases during tests:

from fastapi.testclient import TestClient

from app.main import app, current_user


async def fake_user():
    return {"username": "tester"}


app.dependency_overrides[current_user] = fake_user
client = TestClient(app)


def test_profile():
    response = client.get("/users/me")
    assert response.status_code == 200
    assert response.json()["username"] == "tester"


def teardown_module():
    app.dependency_overrides.clear()

Production and Deployment

Configuration From Environment

Keep secrets and environment-specific values out of committed source:

import os

DATABASE_URL = os.environ["DATABASE_URL"]
SECRET_KEY = os.environ["SECRET_KEY"]

Run for Production

fastapi run app/main.py

Development reload mode is useful locally, but it should not be used as the production process strategy. Deploy behind HTTPS, configure allowed hosts and CORS intentionally, and ensure database migrations run as part of release operations.

Container Example

FROM python:3.12-slim

WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app
CMD ["fastapi", "run", "app/main.py", "--port", "8000"]

Common Mistakes

ProblemBetter approach
Returning passwords or private fieldsUse a dedicated response_model
Blocking network or disk work inside async defUse async libraries or run synchronous work appropriately
Putting every route in main.pySplit endpoints into APIRouter modules
Creating a database session without cleanupProvide it through a yield dependency
Trusting tokens without verificationValidate credentials and authorization server-side
Using in-process background tasks for critical workUse a durable task queue

Quick Reference

pip install "fastapi[standard]"      # Install framework and common server tooling
fastapi dev main.py                  # Run development server with reload
fastapi run app/main.py              # Run production-mode server
pytest                               # Run tests
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

app = FastAPI()

@app.get("/resource/{resource_id}")       # Read
@app.post("/resource", status_code=201)   # Create
@app.put("/resource/{resource_id}")       # Replace
@app.patch("/resource/{resource_id}")     # Partially update
@app.delete("/resource/{resource_id}")    # Delete

Further Reading

On this page