FastAPI with OpenAPI

FastAPI with automatic OpenAPI documentation using Pydantic models and docstrings.


Installation

1pip install fastapi uvicorn[standard]
2pip install python-multipart  # For form data

Basic FastAPI App

 1from fastapi import FastAPI
 2
 3app = FastAPI(
 4    title="My API",
 5    description="API documentation",
 6    version="1.0.0",
 7    docs_url="/docs",  # Swagger UI
 8    redoc_url="/redoc",  # ReDoc
 9    openapi_url="/openapi.json"
10)
11
12@app.get("/")
13def read_root():
14    """
15    Root endpoint
16    
17    Returns a welcome message.
18    """
19    return {"message": "Hello World"}
20
21# Run with: uvicorn main:app --reload

Using Pydantic Models

  1from fastapi import FastAPI, HTTPException
  2from pydantic import BaseModel, Field
  3from typing import List, Optional
  4from datetime import datetime
  5
  6app = FastAPI(title="User API", version="1.0.0")
  7
  8# Request models
  9class UserCreate(BaseModel):
 10    """User creation request"""
 11    username: str = Field(..., min_length=3, max_length=50, description="Username")
 12    email: str = Field(..., description="Email address")
 13    password: str = Field(..., min_length=8, description="Password")
 14    full_name: Optional[str] = Field(None, description="Full name")
 15    
 16    class Config:
 17        json_schema_extra = {
 18            "example": {
 19                "username": "johndoe",
 20                "email": "john@example.com",
 21                "password": "secretpassword",
 22                "full_name": "John Doe"
 23            }
 24        }
 25
 26class UserUpdate(BaseModel):
 27    """User update request"""
 28    email: Optional[str] = Field(None, description="Email address")
 29    full_name: Optional[str] = Field(None, description="Full name")
 30
 31# Response models
 32class User(BaseModel):
 33    """User response"""
 34    id: int = Field(..., description="User ID")
 35    username: str = Field(..., description="Username")
 36    email: str = Field(..., description="Email address")
 37    full_name: Optional[str] = Field(None, description="Full name")
 38    created_at: datetime = Field(..., description="Creation timestamp")
 39    is_active: bool = Field(True, description="Is user active")
 40    
 41    class Config:
 42        json_schema_extra = {
 43            "example": {
 44                "id": 1,
 45                "username": "johndoe",
 46                "email": "john@example.com",
 47                "full_name": "John Doe",
 48                "created_at": "2024-12-12T10:00:00",
 49                "is_active": True
 50            }
 51        }
 52
 53# Fake database
 54users_db: List[User] = []
 55user_id_counter = 1
 56
 57@app.post(
 58    "/users/",
 59    response_model=User,
 60    status_code=201,
 61    tags=["users"],
 62    summary="Create a new user",
 63    description="Create a new user with the provided information"
 64)
 65def create_user(user: UserCreate):
 66    """
 67    Create a new user.
 68    
 69    Args:
 70        user: User creation data
 71    
 72    Returns:
 73        Created user
 74    
 75    Raises:
 76        HTTPException: If username already exists
 77    """
 78    global user_id_counter
 79    
 80    # Check if username exists
 81    if any(u.username == user.username for u in users_db):
 82        raise HTTPException(status_code=400, detail="Username already exists")
 83    
 84    # Create user
 85    new_user = User(
 86        id=user_id_counter,
 87        username=user.username,
 88        email=user.email,
 89        full_name=user.full_name,
 90        created_at=datetime.now(),
 91        is_active=True
 92    )
 93    users_db.append(new_user)
 94    user_id_counter += 1
 95    
 96    return new_user
 97
 98@app.get(
 99    "/users/",
100    response_model=List[User],
101    tags=["users"],
102    summary="List all users",
103    description="Get a list of all users"
104)
105def list_users(
106    skip: int = Field(0, ge=0, description="Number of records to skip"),
107    limit: int = Field(10, ge=1, le=100, description="Maximum number of records to return")
108):
109    """
110    List all users with pagination.
111    
112    Args:
113        skip: Number of records to skip
114        limit: Maximum number of records to return
115    
116    Returns:
117        List of users
118    """
119    return users_db[skip:skip + limit]
120
121@app.get(
122    "/users/{user_id}",
123    response_model=User,
124    tags=["users"],
125    summary="Get user by ID",
126    description="Get a specific user by their ID"
127)
128def get_user(user_id: int = Field(..., description="User ID")):
129    """
130    Get a user by ID.
131    
132    Args:
133        user_id: User ID
134    
135    Returns:
136        User object
137    
138    Raises:
139        HTTPException: If user not found
140    """
141    for user in users_db:
142        if user.id == user_id:
143            return user
144    raise HTTPException(status_code=404, detail="User not found")
145
146@app.put(
147    "/users/{user_id}",
148    response_model=User,
149    tags=["users"],
150    summary="Update user",
151    description="Update user information"
152)
153def update_user(
154    user_id: int = Field(..., description="User ID"),
155    user_update: UserUpdate = None
156):
157    """
158    Update a user.
159    
160    Args:
161        user_id: User ID
162        user_update: User update data
163    
164    Returns:
165        Updated user
166    
167    Raises:
168        HTTPException: If user not found
169    """
170    for user in users_db:
171        if user.id == user_id:
172            if user_update.email is not None:
173                user.email = user_update.email
174            if user_update.full_name is not None:
175                user.full_name = user_update.full_name
176            return user
177    raise HTTPException(status_code=404, detail="User not found")
178
179@app.delete(
180    "/users/{user_id}",
181    status_code=204,
182    tags=["users"],
183    summary="Delete user",
184    description="Delete a user by ID"
185)
186def delete_user(user_id: int = Field(..., description="User ID")):
187    """
188    Delete a user.
189    
190    Args:
191        user_id: User ID
192    
193    Raises:
194        HTTPException: If user not found
195    """
196    for i, user in enumerate(users_db):
197        if user.id == user_id:
198            users_db.pop(i)
199            return
200    raise HTTPException(status_code=404, detail="User not found")

Using Dataclasses

 1from fastapi import FastAPI, HTTPException
 2from dataclasses import dataclass, field
 3from typing import List, Optional
 4from datetime import datetime
 5
 6app = FastAPI(title="Product API", version="1.0.0")
 7
 8@dataclass
 9class Product:
10    """Product model"""
11    id: int
12    name: str
13    description: str
14    price: float
15    quantity: int
16    created_at: datetime = field(default_factory=datetime.now)
17    
18    def __post_init__(self):
19        if self.price < 0:
20            raise ValueError("Price cannot be negative")
21        if self.quantity < 0:
22            raise ValueError("Quantity cannot be negative")
23
24@dataclass
25class ProductCreate:
26    """Product creation request"""
27    name: str
28    description: str
29    price: float
30    quantity: int
31
32# Fake database
33products_db: List[Product] = []
34product_id_counter = 1
35
36@app.post("/products/", response_model=Product, status_code=201, tags=["products"])
37def create_product(product: ProductCreate):
38    """
39    Create a new product.
40    
41    Args:
42        product: Product creation data
43            - name: Product name (required)
44            - description: Product description (required)
45            - price: Product price (required, must be >= 0)
46            - quantity: Product quantity (required, must be >= 0)
47    
48    Returns:
49        Created product with ID and timestamp
50    
51    Raises:
52        HTTPException: If validation fails
53    """
54    global product_id_counter
55    
56    try:
57        new_product = Product(
58            id=product_id_counter,
59            name=product.name,
60            description=product.description,
61            price=product.price,
62            quantity=product.quantity
63        )
64        products_db.append(new_product)
65        product_id_counter += 1
66        return new_product
67    except ValueError as e:
68        raise HTTPException(status_code=400, detail=str(e))
69
70@app.get("/products/", response_model=List[Product], tags=["products"])
71def list_products():
72    """
73    List all products.
74    
75    Returns:
76        List of all products in the database
77    """
78    return products_db
79
80@app.get("/products/{product_id}", response_model=Product, tags=["products"])
81def get_product(product_id: int):
82    """
83    Get a product by ID.
84    
85    Args:
86        product_id: Product ID
87    
88    Returns:
89        Product object
90    
91    Raises:
92        HTTPException: If product not found (404)
93    """
94    for product in products_db:
95        if product.id == product_id:
96            return product
97    raise HTTPException(status_code=404, detail="Product not found")

Response Models and Status Codes

 1from fastapi import FastAPI, HTTPException, status
 2from pydantic import BaseModel
 3from typing import Optional
 4
 5app = FastAPI()
 6
 7class ErrorResponse(BaseModel):
 8    """Error response"""
 9    detail: str
10    error_code: Optional[str] = None
11
12class SuccessResponse(BaseModel):
13    """Success response"""
14    message: str
15    data: Optional[dict] = None
16
17@app.get(
18    "/items/{item_id}",
19    response_model=SuccessResponse,
20    responses={
21        200: {
22            "description": "Successful response",
23            "content": {
24                "application/json": {
25                    "example": {"message": "Item found", "data": {"id": 1, "name": "Item"}}
26                }
27            }
28        },
29        404: {
30            "description": "Item not found",
31            "model": ErrorResponse,
32            "content": {
33                "application/json": {
34                    "example": {"detail": "Item not found", "error_code": "ITEM_NOT_FOUND"}
35                }
36            }
37        }
38    },
39    tags=["items"]
40)
41def get_item(item_id: int):
42    """
43    Get an item by ID.
44    
45    This endpoint retrieves an item from the database.
46    
47    Args:
48        item_id: The ID of the item to retrieve
49    
50    Returns:
51        SuccessResponse with item data
52    
53    Raises:
54        HTTPException: 404 if item not found
55    """
56    if item_id == 1:
57        return SuccessResponse(
58            message="Item found",
59            data={"id": 1, "name": "Sample Item"}
60        )
61    raise HTTPException(
62        status_code=status.HTTP_404_NOT_FOUND,
63        detail="Item not found"
64    )

Path Operations with Tags and Metadata

 1from fastapi import FastAPI
 2from pydantic import BaseModel
 3
 4app = FastAPI(
 5    title="My API",
 6    description="Comprehensive API documentation",
 7    version="1.0.0",
 8    openapi_tags=[
 9        {
10            "name": "users",
11            "description": "Operations with users. The **login** logic is also here.",
12        },
13        {
14            "name": "items",
15            "description": "Manage items. So _fancy_ they have their own docs.",
16            "externalDocs": {
17                "description": "Items external docs",
18                "url": "https://example.com/items",
19            },
20        },
21    ]
22)
23
24class Item(BaseModel):
25    name: str
26    description: str
27
28@app.post(
29    "/items/",
30    tags=["items"],
31    summary="Create an item",
32    description="Create an item with all the information",
33    response_description="The created item"
34)
35def create_item(item: Item):
36    """
37    Create an item with all the information:
38    
39    - **name**: each item must have a name
40    - **description**: a long description
41    """
42    return item
43
44@app.get(
45    "/items/",
46    tags=["items"],
47    summary="List items",
48    deprecated=False
49)
50def list_items():
51    """List all items in the database."""
52    return [{"name": "Item 1"}, {"name": "Item 2"}]

Query Parameters and Validation

 1from fastapi import FastAPI, Query
 2from typing import Optional, List
 3from pydantic import BaseModel
 4
 5app = FastAPI()
 6
 7class SearchResults(BaseModel):
 8    """Search results"""
 9    query: str
10    results: List[dict]
11    total: int
12
13@app.get("/search/", response_model=SearchResults, tags=["search"])
14def search(
15    q: str = Query(
16        ...,
17        min_length=3,
18        max_length=50,
19        description="Search query",
20        example="python"
21    ),
22    page: int = Query(
23        1,
24        ge=1,
25        description="Page number",
26        example=1
27    ),
28    size: int = Query(
29        10,
30        ge=1,
31        le=100,
32        description="Page size",
33        example=10
34    ),
35    sort: Optional[str] = Query(
36        None,
37        regex="^(asc|desc)$",
38        description="Sort order",
39        example="asc"
40    ),
41    tags: Optional[List[str]] = Query(
42        None,
43        description="Filter by tags",
44        example=["python", "fastapi"]
45    )
46):
47    """
48    Search for items.
49    
50    Args:
51        q: Search query (3-50 characters)
52        page: Page number (>= 1)
53        size: Page size (1-100)
54        sort: Sort order (asc or desc)
55        tags: Filter by tags
56    
57    Returns:
58        Search results with pagination
59    """
60    return SearchResults(
61        query=q,
62        results=[{"id": 1, "name": "Result 1"}],
63        total=1
64    )

Request Body Examples

 1from fastapi import FastAPI, Body
 2from pydantic import BaseModel, Field
 3from typing import Optional
 4
 5app = FastAPI()
 6
 7class Item(BaseModel):
 8    name: str = Field(..., example="Widget")
 9    description: Optional[str] = Field(None, example="A useful widget")
10    price: float = Field(..., gt=0, example=9.99)
11    tax: Optional[float] = Field(None, ge=0, example=0.99)
12
13@app.post("/items/", tags=["items"])
14def create_item(
15    item: Item = Body(
16        ...,
17        examples={
18            "normal": {
19                "summary": "A normal example",
20                "description": "A **normal** item works correctly.",
21                "value": {
22                    "name": "Widget",
23                    "description": "A very nice widget",
24                    "price": 35.4,
25                    "tax": 3.2,
26                },
27            },
28            "minimal": {
29                "summary": "Minimal example",
30                "value": {
31                    "name": "Widget",
32                    "price": 35.4,
33                },
34            },
35            "invalid": {
36                "summary": "Invalid data is rejected",
37                "value": {
38                    "name": "Widget",
39                    "price": -10,  # Invalid: negative price
40                },
41            },
42        },
43    )
44):
45    """
46    Create an item.
47    
48    The request body should include:
49    - **name**: Item name (required)
50    - **description**: Item description (optional)
51    - **price**: Item price (required, must be > 0)
52    - **tax**: Tax amount (optional, must be >= 0)
53    """
54    return item

Dependencies and Security

 1from fastapi import FastAPI, Depends, HTTPException, status
 2from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 3from pydantic import BaseModel
 4
 5app = FastAPI()
 6security = HTTPBearer()
 7
 8class User(BaseModel):
 9    username: str
10    email: str
11
12def get_current_user(
13    credentials: HTTPAuthorizationCredentials = Depends(security)
14) -> User:
15    """
16    Get current authenticated user.
17    
18    Args:
19        credentials: Bearer token
20    
21    Returns:
22        Current user
23    
24    Raises:
25        HTTPException: If token is invalid
26    """
27    token = credentials.credentials
28    # Validate token (simplified)
29    if token != "valid-token":
30        raise HTTPException(
31            status_code=status.HTTP_401_UNAUTHORIZED,
32            detail="Invalid authentication credentials",
33            headers={"WWW-Authenticate": "Bearer"},
34        )
35    return User(username="johndoe", email="john@example.com")
36
37@app.get(
38    "/users/me",
39    response_model=User,
40    tags=["users"],
41    summary="Get current user",
42    description="Get the current authenticated user's information"
43)
44def read_users_me(current_user: User = Depends(get_current_user)):
45    """
46    Get current user information.
47    
48    Requires authentication via Bearer token.
49    
50    Returns:
51        Current user object
52    """
53    return current_user

File Upload

 1from fastapi import FastAPI, File, UploadFile
 2from typing import List
 3
 4app = FastAPI()
 5
 6@app.post("/upload/", tags=["files"])
 7async def upload_file(file: UploadFile = File(...)):
 8    """
 9    Upload a single file.
10    
11    Args:
12        file: File to upload
13    
14    Returns:
15        File information
16    """
17    return {
18        "filename": file.filename,
19        "content_type": file.content_type,
20        "size": len(await file.read())
21    }
22
23@app.post("/uploadfiles/", tags=["files"])
24async def upload_files(files: List[UploadFile] = File(...)):
25    """
26    Upload multiple files.
27    
28    Args:
29        files: List of files to upload
30    
31    Returns:
32        List of file information
33    """
34    return [
35        {
36            "filename": file.filename,
37            "content_type": file.content_type,
38        }
39        for file in files
40    ]

Custom OpenAPI Schema

 1from fastapi import FastAPI
 2from fastapi.openapi.utils import get_openapi
 3
 4app = FastAPI()
 5
 6def custom_openapi():
 7    if app.openapi_schema:
 8        return app.openapi_schema
 9    
10    openapi_schema = get_openapi(
11        title="Custom API",
12        version="2.5.0",
13        description="This is a custom OpenAPI schema",
14        routes=app.routes,
15    )
16    
17    # Add custom fields
18    openapi_schema["info"]["x-logo"] = {
19        "url": "https://example.com/logo.png"
20    }
21    
22    app.openapi_schema = openapi_schema
23    return app.openapi_schema
24
25app.openapi = custom_openapi
26
27@app.get("/")
28def read_root():
29    """Root endpoint"""
30    return {"message": "Hello World"}

Run Application

1# Development
2uvicorn main:app --reload
3
4# Production
5uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
6
7# With Gunicorn
8gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker

Access Documentation

1# Swagger UI (interactive)
2http://localhost:8000/docs
3
4# ReDoc (alternative)
5http://localhost:8000/redoc
6
7# OpenAPI JSON schema
8http://localhost:8000/openapi.json

Related Snippets