How to Document an API for Python FastAPI: Best Practices for Maintainable and Readable Code

Emanuele
CodeX
Published in
6 min readMay 21, 2024

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:

You can run FastApi and go in http://localhost:8000/redoc to see this visual

Or we can see in the traditional Swagger UI:

You can run FastApi and go in http://localhost:8000/docs to see this visual

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!

--

--

Emanuele
CodeX
Writer for

LA-based robotics engineer specializing in Azure technologies. Passionate about system design, AI, and entrepreneurship, dedicated to driving tech innovation