How to Document an API for Python FastAPI: Best Practices for Maintainable and Readable Code
In the fast-paced world of software development, maintaining readable and maintainable code is crucial, especially when working with APIs. Python’s FastAPI framework has gained popularity due to its simplicity, speed, and robustness. However, an often-overlooked aspect is the importance of good documentation. Well-documented APIs not only make it easier for other developers (and your future self) to understand and use your code but also enhance the overall quality and maintainability of your project.
In this article, we’ll explore best practices for documenting an API using FastAPI to ensure your code remains clean, maintainable, and readable.
We will see in this article how create a documentation like this one:
Or we can see in the traditional Swagger UI:
Let us go step by step:
1. Start with Clear and Concise Docstrings
Docstrings are an essential part of Python documentation. FastAPI leverages Pydantic models and Python type hints, making it easier to auto-generate documentation. Here’s how to make the most of docstrings:
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, description="Query string for the items to search for")):
"""
Retrieve items based on a query string.
- **q**: Optional query string to search for items
"""
return {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
Best Practices:
- Describe the endpoint: Clearly explain what the endpoint does.
- Document parameters: Mention all parameters, their types, and their roles.
- Explain the response: Provide details on what the endpoint return
2. Use FastAPI’s Built-in Documentation
FastAPI automatically generates interactive API documentation using Swagger UI and Redoc. Make sure to utilize these features to their fullest potential. The documentation is available by default at /docs
(Swagger UI) and /redoc
(Redoc).
from fastapi import FastAPI
app = FastAPI(
title="My API with documentation",
description="This is a very fancy FastAPI project, with auto docs for the API.",
version="1.0.0",
contact={
"name": "Emanuele Orecchio",
"url": "https://medium.com/@emanueleorecchio",
"email": "emanueleorecchio@thisisnotmyemail.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
)
)
Best Practices:
- Provide metadata: Use the FastAPI application parameters to add title, description, version, contact, and license information.
- Customize descriptions: Provide detailed descriptions for each endpoint and parameter directly in the code.
3. Leverage Pydantic Models for Request and Response Bodies
Using Pydantic models not only enforces data validation but also helps in automatically generating schema documentation.
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
This validation will integrate this part of the documentation of each parameter:
4. Write Comprehensive README and Additional Documentation
While in-code documentation is crucial, a comprehensive README file provides an overview of your project and instructions for getting started.
I have create a folder in my project called api_docs
with a file called put_items_itemid.md, <type of RestApi>_<path>_<input>.md
# FastAPI Item API Documentation
This API allows you to update an item.
## PUT /items/{item_id}
Updates an item.
### Parameters
- `item_id` (path, required): The ID of the item to update. Must be an integer.
- `item` (body, required): The item to update. Must be a JSON object with the following properties:
- `name` (string, required): The name of the item.
- `description` (string, optional): The description of the item. If provided, must be a string of maximum 300 characters.
- `price` (number, required): The price of the item. Must be a number greater than zero.
- `tax` (number, optional): The tax of the item. If provided, must be a number.
### Example
Request:
```http
PUT /items/1 HTTP/1.1
Content-Type: application/json
{
"item": {
"name": "Item 1",
"description": "This is item 1",
"price": 9.99,
"tax": 0.99
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"item_id": 1,
"item": {
"name": "Item 1",
"description": "This is item 1",
"price": 9.99,
"tax": 0.99
}
}
And we can add the link to the API:
from fastapi import FastAPI, Form, Request, status, Depends, Body
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn
from pydantic import BaseModel, Field
from typing import Annotated
from pathlib import Path
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
@app.put("/items/{item_id}", description=Path('api_docs/put_items_itemid.md').read_text(), summary = 'Put Item')
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
if __name__ == '__main__':
uvicorn.run(app, host="localhost", port=8000)
#to run the app, open a terminal and run the command: uvicorn main:app --reload
Best Practices:
- Project setup: Include steps to set up and run the project.
- Endpoint overview: Summarize available endpoints and their purposes.
- Usage examples: Provide example requests and responses.
5. Maintain Documentation Consistency
Ensure that your documentation is consistent across all parts of the project. This includes:
- Updating docstrings whenever the code changes.
- Synchronizing README and in-code documentation to reflect the current state of the API.
- Using version control to track changes in documentation alongside code changes.
Best Practices:
- Automate checks: Use tools like
doctest
to ensure docstring examples remain accurate. - Regular reviews: Periodically review documentation for accuracy and completeness.
See the documentation created and Import in Postman
This is the full code
from fastapi import FastAPI, Form, Request, status, Depends, Body, Query
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse
import uvicorn
from pydantic import BaseModel, Field
from typing import Annotated
from pathlib import Path
from typing import Optional
app = FastAPI(
title="My API with documentation",
description="This is a very fancy FastAPI project, with auto docs for the API.",
version="1.0.0",
contact={
"name": "Emanuele Orecchio",
"url": "https://medium.com/@emanueleorecchio",
"email": "emanueleorecchio@thisisnotmyemail.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
)
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, description="Query string for the items to search for")):
"""
Retrieve items based on a query string.
- **q**: Optional query string to search for items
"""
return {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
@app.put("/items/{item_id}", description=Path('api_docs/put_items_itemid.md').read_text(), summary = 'Put Item')
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
if __name__ == '__main__':
uvicorn.run(app, host="localhost", port=8000)
#to run the app, open a terminal and run the command: uvicorn main:app --reload
You can run it locally using the command uvicorn main:app --reload
Too see the documentations check http://localhost:8000/docs
or http://localhost:8000/redoc
If you prefer to test the API on postman, you can import the API using directly the URL: localhost:8000/openapi.json — here the official documentation Import Swagger APIs | Postman Learning Center
Conclusion
Good documentation is a cornerstone of maintainable and readable code. By following these best practices with FastAPI, you can ensure that your API remains easy to understand and use for both current and future developers. Start with clear and concise docstrings, leverage FastAPI’s built-in documentation, use Pydantic models, provide comprehensive README files, and maintain documentation consistency. With these strategies, your API will not only function well but also be a pleasure to work with.
Happy documenting!