FastAPI ReDoc
In this tutorial, you'll learn how to use FastAPI's built-in ReDoc integration to create beautiful, interactive API documentation for your application.
Introduction to ReDoc
ReDoc is a powerful, open-source tool that generates API documentation from OpenAPI specifications. FastAPI automatically integrates ReDoc, providing your API with professional and interactive documentation without any additional effort.
Unlike Swagger UI (which is also included in FastAPI), ReDoc offers a different visual style and user experience:
- A three-panel, responsive layout
- Better support for nested objects
- Improved readability for large APIs
- Support for deep linking
How FastAPI Integrates ReDoc
FastAPI automatically generates OpenAPI documentation (previously known as Swagger) for your API and makes it available in the ReDoc format. This happens behind the scenes with no extra code required from you!
Accessing ReDoc Documentation
When you create a FastAPI application, the ReDoc documentation is automatically available at the /redoc
endpoint. For example, if your API is running on http://localhost:8000
, you can access the ReDoc documentation at:
http://localhost:8000/redoc
Basic Setup
Let's create a simple API and see how ReDoc documentation works:
from fastapi import FastAPI
app = FastAPI(
title="My Amazing API",
description="This is a sample API to demonstrate ReDoc documentation",
version="0.1.0"
)
@app.get("/")
def read_root():
"""
Return a simple greeting message.
"""
return {"message": "Hello World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, query: str = None):
"""
Fetch an item by its ID.
- **item_id**: The ID of the item to retrieve
- **query**: Optional search query parameter
"""
return {"item_id": item_id, "query": query}
When you run this application and navigate to /redoc
, you'll see beautiful documentation automatically generated for your API, including:
- API title, description, and version
- All endpoints organized by tags
- Request parameters and response models
- Docstring comments as descriptions
Customizing ReDoc Documentation
Adding Metadata
You can customize the overall API documentation by providing metadata in the FastAPI
constructor:
app = FastAPI(
title="Pet Store API",
description="""
This API helps you manage your pet store inventory.
## Features
* **Create** and manage pets
* **Track** inventory
* **Process** orders
""",
version="1.0.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",
},
)
Documenting Path Operations
To make your ReDoc documentation more useful, add docstrings and parameter descriptions:
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
active: Optional[bool] = Query(
None,
title="Active Status",
description="Filter users by active status"
)
):
"""
Retrieve a user by ID.
This endpoint will return user details based on the path parameter.
If the active query parameter is provided, it will filter by status.
**Note**: This endpoint requires authentication.
"""
return {"user_id": user_id, "active": active}
Adding Examples
You can enhance your documentation by providing examples:
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., example="Smartphone")
description: Optional[str] = Field(None, example="A high-end smartphone with great camera")
price: float = Field(..., example=999.99)
tax: Optional[float] = Field(None, example=87.5)
class Config:
schema_extra = {
"example": {
"name": "Laptop",
"description": "A powerful laptop for developers",
"price": 1299.99,
"tax": 115.99
}
}
@app.post("/items/")
async def create_item(item: Item):
"""
Create a new item in the inventory.
The request body should contain item details.
"""
return item
Organizing with Tags
You can organize your endpoints in ReDoc using tags:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/", tags=["users"])
async def get_users():
"""Get all users."""
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
@app.post("/users/", tags=["users"])
async def create_user():
"""Create a new user."""
return {"id": 3, "name": "Charlie"}
@app.get("/items/", tags=["items"])
async def get_items():
"""Get all items."""
return [{"id": 1, "name": "Hammer"}, {"id": 2, "name": "Screwdriver"}]
@app.post("/items/", tags=["items"])
async def create_item():
"""Create a new item."""
return {"id": 3, "name": "Wrench"}
In ReDoc, these endpoints will be grouped under "users" and "items" sections, making your documentation more organized.
Customizing ReDoc Appearance
Disabling ReDoc
If you want to disable ReDoc for some reason, you can do so by setting docs_url
or redoc_url
to None
:
# Disable ReDoc, keep Swagger UI
app = FastAPI(redoc_url=None)
# Disable both Swagger UI and ReDoc
app = FastAPI(docs_url=None, redoc_url=None)
Changing the URL Path
You can customize the URL path for ReDoc:
app = FastAPI(redoc_url="/api-docs")
Now ReDoc will be available at /api-docs
instead of the default /redoc
.
Real-World Example: Building a Movie API
Let's create a more complex example—a movie review API:
from fastapi import FastAPI, Path, Query, HTTPException
from typing import List, Optional
from pydantic import BaseModel, Field
from enum import Enum
app = FastAPI(
title="MovieReviews API",
description="An API for managing movie reviews and ratings",
version="1.0.0"
)
class Genre(str, Enum):
ACTION = "action"
COMEDY = "comedy"
DRAMA = "drama"
HORROR = "horror"
SCIFI = "sci-fi"
ROMANCE = "romance"
class MovieBase(BaseModel):
title: str = Field(..., example="The Matrix")
director: str = Field(..., example="Lana and Lilly Wachowski")
year: int = Field(..., ge=1900, le=2100, example=1999)
genre: Genre = Field(..., example=Genre.SCIFI)
class Config:
schema_extra = {
"example": {
"title": "Inception",
"director": "Christopher Nolan",
"year": 2010,
"genre": "sci-fi"
}
}
class Movie(MovieBase):
id: int
rating: Optional[float] = Field(None, ge=0, le=10, example=8.5)
class ReviewBase(BaseModel):
text: str = Field(..., min_length=5, max_length=500, example="This movie was fantastic!")
rating: float = Field(..., ge=0, le=10, example=9.0)
class Review(ReviewBase):
id: int
movie_id: int
user_id: int
# Sample data
movies_db = [
{"id": 1, "title": "The Shawshank Redemption", "director": "Frank Darabont",
"year": 1994, "genre": "drama", "rating": 9.3},
{"id": 2, "title": "The Godfather", "director": "Francis Ford Coppola",
"year": 1972, "genre": "drama", "rating": 9.2},
]
reviews_db = [
{"id": 1, "movie_id": 1, "user_id": 1, "text": "A masterpiece!", "rating": 9.5},
{"id": 2, "movie_id": 1, "user_id": 2, "text": "Incredible story and acting", "rating": 9.0},
]
# Endpoints
@app.get("/movies/", response_model=List[Movie], tags=["movies"])
async def get_movies(
genre: Optional[Genre] = Query(None, description="Filter movies by genre"),
year: Optional[int] = Query(None, description="Filter movies by release year")
):
"""
Retrieve a list of all movies with optional filtering.
You can filter the results by genre and/or release year.
"""
filtered_movies = movies_db
if genre:
filtered_movies = [m for m in filtered_movies if m["genre"] == genre]
if year:
filtered_movies = [m for m in filtered_movies if m["year"] == year]
return filtered_movies
@app.get("/movies/{movie_id}", response_model=Movie, tags=["movies"])
async def get_movie(
movie_id: int = Path(..., title="The ID of the movie", ge=1)
):
"""
Retrieve detailed information about a specific movie by its ID.
"""
for movie in movies_db:
if movie["id"] == movie_id:
return movie
raise HTTPException(status_code=404, detail="Movie not found")
@app.get("/movies/{movie_id}/reviews", response_model=List[Review], tags=["reviews"])
async def get_movie_reviews(movie_id: int):
"""
Retrieve all reviews for a specific movie by its ID.
"""
# First check if movie exists
movie_exists = any(movie["id"] == movie_id for movie in movies_db)
if not movie_exists:
raise HTTPException(status_code=404, detail="Movie not found")
return [r for r in reviews_db if r["movie_id"] == movie_id]
@app.post("/movies/{movie_id}/reviews", response_model=Review, tags=["reviews"])
async def create_review(movie_id: int, review: ReviewBase):
"""
Create a new review for a specific movie.
The request body should include review text and rating.
"""
# This is simplified (in a real app, you would save to a database)
new_review = {
"id": len(reviews_db) + 1,
"movie_id": movie_id,
"user_id": 1, # In a real app, this would come from authentication
**review.dict()
}
reviews_db.append(new_review)
return new_review
When you navigate to the /redoc
endpoint, you'll see a beautiful, organized API documentation with:
- Movie and review models clearly documented
- Endpoints grouped into "movies" and "reviews" sections
- Request and response schemas with examples
- Clear descriptions for each endpoint and parameter
Summary
FastAPI's ReDoc integration provides an elegant way to automatically document your API. In this tutorial, you've learned:
- How FastAPI integrates with ReDoc out of the box
- How to access and customize the ReDoc documentation
- Different ways to enhance your API documentation with examples, descriptions, and tags
- How to structure a real-world API with ReDoc documentation
By leveraging ReDoc, you can ensure that your API is not only functional but also easily understandable and usable by other developers.
Further Resources
Exercises
- Create a simple FastAPI application with at least two endpoints and view the generated ReDoc documentation.
- Enhance the documentation with detailed descriptions, examples, and parameter explanations.
- Organize your API endpoints using tags and observe how this affects the ReDoc layout.
- Try customizing the ReDoc URL path and experiment with disabling/enabling it.
- Create a more complex API with nested models and see how ReDoc handles the documentation for these complex structures.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)