Skip to main content

FastAPI Swagger UI

Introduction

One of FastAPI's most powerful features is its automatic interactive API documentation. When you build an API with FastAPI, you get a beautiful, interactive documentation interface called Swagger UI for free. This documentation is automatically generated based on your API routes, parameters, and models.

In this tutorial, we'll explore how FastAPI integrates with Swagger UI, how to access it, customize it, and make the most of this powerful feature to help others understand and use your API.

What is Swagger UI?

Swagger UI is an open-source tool that generates a responsive web UI for OpenAPI specifications. It allows developers and users of your API to:

  • View all API endpoints in one place
  • See request parameters and response models
  • Test API calls directly from the browser
  • Understand authentication requirements

FastAPI automatically generates the OpenAPI schema for your API and serves it through Swagger UI, making your API self-documenting.

Accessing Swagger UI in FastAPI

When you create a FastAPI application, the Swagger UI documentation is automatically available at /docs. Let's see a basic example:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items():
return {"items": ["Item1", "Item2"]}

@app.post("/items/")
async def create_item(name: str):
return {"item_name": name}

After running your application with Uvicorn:

bash
uvicorn main:app --reload

You can access the Swagger UI by visiting http://localhost:8000/docs in your browser. You'll see an interactive documentation interface that looks like this:

Swagger UI Interface

Customizing the Swagger UI

FastAPI allows you to customize the appearance and behavior of your Swagger UI documentation. Here are some common customizations:

Modifying API Metadata

You can customize the title, description, version, and more when initializing your FastAPI application:

python
from fastapi import FastAPI

app = FastAPI(
title="My Super API",
description="This API does awesome things",
version="0.1.0",
terms_of_service="http://example.com/terms/",
contact={
"name": "API Support",
"url": "http://example.com/support",
"email": "[email protected]",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)

@app.get("/")
async def root():
return {"message": "Hello World"}

Organizing with Tags

You can organize your API endpoints into groups using tags:

python
from fastapi import FastAPI, APIRouter

app = FastAPI()

router = APIRouter(
prefix="/users",
tags=["Users"],
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]

app.include_router(router)

@app.get("/items/", tags=["Items"])
async def read_items():
return [{"name": "Portal Gun"}, {"name": "Plumbus"}]

Adding Descriptions to Your Endpoints

You can add detailed descriptions to your endpoints using docstrings or the summary and description parameters:

python
from fastapi import FastAPI

app = FastAPI()

@app.get(
"/items/{item_id}",
summary="Read Item",
description="Get details about a specific item by its ID",
response_description="The item details",
)
async def read_item(item_id: int):
"""
Get an item by its ID.

This endpoint will return details about a specific item.

- **item_id**: The unique identifier of the item
"""
return {"item_id": item_id, "name": f"Fake Item {item_id}"}

Hiding an Endpoint from Docs

Sometimes you might want to hide certain endpoints from the documentation:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/visible/")
async def visible_endpoint():
return {"message": "You can see this in the docs"}

@app.get("/hidden/", include_in_schema=False)
async def hidden_endpoint():
return {"message": "You cannot see this in the docs"}

Documenting Request Body and Response Models

One of the most powerful features of FastAPI's Swagger UI is how it handles request and response models using Pydantic. Let's see how to properly document them:

Request Body Documentation

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
name: str = Field(..., description="The name of the item")
description: str = Field(None, description="Optional description of the item")
price: float = Field(..., description="The price of the item", gt=0)
tax: float = Field(None, description="The tax rate for the item")

class Config:
schema_extra = {
"example": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
}

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

Response Model Documentation

python
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

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

@app.get("/items/", response_model=List[Item], response_description="List of available items")
async def read_items():
return [
{"name": "Item1", "price": 14.5, "is_offer": True},
{"name": "Item2", "price": 25.0, "is_offer": False}
]

Adding Authentication to Swagger UI

If your API uses security mechanisms, you can configure Swagger UI to include authentication:

python
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import APIKeyHeader

app = FastAPI()

api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)

@app.get("/secure-endpoint/")
async def secure_endpoint(api_key: str = Security(api_key_header)):
if api_key != "secret_key":
raise HTTPException(status_code=403, detail="Invalid API Key")
return {"message": "You accessed the secure endpoint"}

For OAuth2 with Password flow:

python
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# In a real app, you would check the username and password
if form_data.username != "testuser" or form_data.password != "testpassword":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return {"access_token": "fake-token", "token_type": "bearer"}

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

Customizing the OpenAPI Schema

For advanced customization, you can modify the OpenAPI schema directly:

python
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI()

@app.get("/items/")
async def read_items():
return [{"name": "Item1"}]

def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
# Custom documentation fastapi-key parameter
openapi_schema["components"]["securitySchemes"] = {
"api_key": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API key for authentication"
}
}
app.openapi_schema = openapi_schema
return app.openapi_schema

app.openapi = custom_openapi

Alternative Documentation: ReDoc

FastAPI also provides ReDoc, an alternative documentation UI. It's available at /redoc and uses the same OpenAPI schema:

python
from fastapi import FastAPI

app = FastAPI(
title="My API",
description="API Documentation with ReDoc",
redoc_url="/documentation", # Custom ReDoc URL (default is /redoc)
docs_url=None, # Disable Swagger UI if you prefer only ReDoc
)

@app.get("/items/")
async def read_items():
return [{"name": "Item1"}]

Real-world Example: Building a Bookstore API

Let's put everything together in a more comprehensive example of a bookstore API with well-documented endpoints:

python
from fastapi import FastAPI, HTTPException, Query, Path, Depends
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum

app = FastAPI(
title="Bookstore API",
description="API for managing a bookstore's inventory",
version="1.0.0",
)

class Genre(str, Enum):
fiction = "fiction"
non_fiction = "non-fiction"
sci_fi = "sci-fi"
mystery = "mystery"

class BookBase(BaseModel):
title: str = Field(..., description="Title of the book")
author: str = Field(..., description="Author of the book")
genre: Genre = Field(..., description="Genre of the book")
price: float = Field(..., gt=0, description="Price of the book in USD")

class BookCreate(BookBase):
pass

class Book(BookBase):
id: int = Field(..., description="Unique identifier for the book")
in_stock: bool = Field(True, description="Whether the book is in stock")

class Config:
schema_extra = {
"example": {
"id": 1,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"genre": "fiction",
"price": 12.99,
"in_stock": True
}
}

# Simulate a database
books_db = [
Book(id=1, title="The Great Gatsby", author="F. Scott Fitzgerald", genre=Genre.fiction, price=12.99),
Book(id=2, title="Sapiens", author="Yuval Noah Harari", genre=Genre.non_fiction, price=15.99),
Book(id=3, title="Dune", author="Frank Herbert", genre=Genre.sci_fi, price=18.50),
]

@app.get("/books/",
response_model=List[Book],
tags=["Books"],
summary="Get all books",
description="Retrieve a list of all books in the inventory")
async def get_books(
genre: Optional[Genre] = Query(None, description="Filter books by genre"),
skip: int = Query(0, ge=0, description="Number of books to skip"),
limit: int = Query(10, ge=1, le=100, description="Maximum number of books to return")
):
"""
Get all books in the inventory with optional filtering.

- **genre**: Filter books by their genre
- **skip**: Number of books to skip (pagination)
- **limit**: Maximum number of books to return (pagination)
"""
filtered_books = books_db
if genre:
filtered_books = [book for book in filtered_books if book.genre == genre]
return filtered_books[skip:skip+limit]

@app.get("/books/{book_id}",
response_model=Book,
tags=["Books"],
summary="Get a book by ID",
responses={404: {"description": "Book not found"}})
async def get_book(book_id: int = Path(..., description="The ID of the book to retrieve", ge=1)):
"""
Get a single book by its ID.

If the book is not found, a 404 error is returned.
"""
for book in books_db:
if book.id == book_id:
return book
raise HTTPException(status_code=404, detail="Book not found")

@app.post("/books/",
response_model=Book,
tags=["Books"],
status_code=201,
summary="Add a new book",
response_description="The created book")
async def create_book(book: BookCreate):
"""
Add a new book to the inventory.

Returns the created book with its assigned ID.
"""
new_book = Book(
id=len(books_db) + 1,
**book.dict()
)
books_db.append(new_book)
return new_book

Summary

FastAPI's Swagger UI integration is a powerful feature that makes your API self-documenting and interactive. We've covered:

  • How to access and use the built-in Swagger UI at /docs
  • Ways to customize API metadata, tags, and endpoint descriptions
  • How to document request bodies and response models with Pydantic
  • Adding authentication to your API and Swagger UI
  • Advanced customization of the OpenAPI schema
  • Using ReDoc as an alternative documentation UI
  • Building a comprehensive, well-documented API example

By properly documenting your FastAPI application, you make it more accessible to other developers (including your future self!) and provide a seamless way for users to understand and interact with your API.

Additional Resources

  1. FastAPI Official Documentation on OpenAPI
  2. Swagger UI GitHub Repository
  3. OpenAPI Specification
  4. ReDoc Documentation

Exercises

  1. Create a simple FastAPI application with at least three endpoints and access the Swagger UI to see how they're documented.
  2. Add detailed descriptions, examples, and response models to your endpoints.
  3. Organize your endpoints using tags and customize the API metadata.
  4. Implement a simple authentication mechanism and configure it in Swagger UI.
  5. Create a custom OpenAPI schema that includes additional documentation for a specific parameter.


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