DEV Community

Cover image for Understanding FastAPI: How OpenAPI works
Rafael de Oliveira Marques
Rafael de Oliveira Marques

Posted on

Understanding FastAPI: How OpenAPI works

We already saw what is ASGI, how starlette works and how FastAPI extends starlatte. Now it's time to see how FastAPI offers one of the greatest features (in my opinion): Out of the box API docs with Swagger and Redoc.

💭 What is OpenAPI?

If we go to the OpenAPI's repository, we'll see that:

The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs. This allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic. When properly defined via OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interface descriptions have done for lower-level programming, the OpenAPI Specification removes guesswork in calling a service.

So the OpenAPI is simply a document that tells you how you can define and implement an API following a standard, making it easier for other developers to understand and consume.

If you have ever worked with any type of systems integration, you know how easy or extremely difficult your life can be, depending on the quality of the integration documentation.

And here is where FastAPI ✨ shines: It leverages Pydantic powerful data validation to offer out of the box JSON Schema and OpenAPI specs via Swagger and Redoc.

How FastAPI offers JSON Schema and OpenAPI?

OpenAPI logo

FastAPI will give you for free OpenAPI docs with both Swagger and Redoc.

The JSON Schema is offered using Pydantic, and if you are using FastAPI, you probably already know it.

🚀 Pydantic and JSON Schema

pydantic logo

See how easy is to generate a JSON Schema with Pydantic:

from pydantic import BaseModel, Field, TypeAdapter


class User(BaseModel):

    id: str = Field(
        ...,
        title="User ID",
        description="User's unique identification"
    )

    name: str = Field(
        ...,
        title="Name",
        description="User's name",
        min_length=5,
        max_length=50
    )


type_adapter = TypeAdapter(User)
Enter fullscreen mode Exit fullscreen mode

Now if we call TypeAdapter's json_schema method, we'll get a valid JSON Schema compliant with OpenAPI Specification v3.1.0:

{
    "properties": {
        "id": {
            "description": "User's unique identification",
            "title": "User ID",
            "type": "string"
        },
        "name": {
            "description": "User's name",
            "maxLength": 50,
            "minLength": 5,
            "title": "Name",
            "type": "string"
        }
    },
    "required": [
        "id",
        "name"
    ],
    "title": "User",
    "type": "object"
}
Enter fullscreen mode Exit fullscreen mode

⚙️ FastAPI documentation setup

When we create a FastAPI instance, it will setup all you need to create the docs.

The 🧙 magic will happen inside setup function, that is called inside __init__.

If we look at setup function, we'll see that FastAPI creates for us an openapi route unless we set the openapi_url=None.

if self.openapi_url:
    urls = (server_data.get("url") for server_data in self.servers)
    server_urls = {url for url in urls if url}

    async def openapi(req: Request) -> JSONResponse:
        root_path = req.scope.get("root_path", "").rstrip("/")
        if root_path not in server_urls:
            if root_path and self.root_path_in_servers:
                self.servers.insert(0, {"url": root_path})
                server_urls.add(root_path)
        return JSONResponse(self.openapi())

    self.add_route(self.openapi_url, openapi, include_in_schema=False)
Enter fullscreen mode Exit fullscreen mode

FastAPI is setting up here the /openapi.json route, that will call the get_openapi function. It will return all the Schema from our project, or more precisely, an OpenAPI object defined inside openapi module.

So the order of events is:

When you create your app, FastAPI will add the routes:

  • /openapi.json
  • /docs
  • /redoc

if you don't remove it explicitly.

The /openapi.json route will return a JSONResponse containing all needed data from your routes when called.

The /docs and /redoc routes will basically return an HTML that loads the swagger and redoc respectively.

📑 Minimal example

If we create a minimal application:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def hello():
    return {"Hello": "world :D"}
Enter fullscreen mode Exit fullscreen mode

We can see our swagger docs calling /docs:

FastAPI swagger docs

And redoc docs calling /redoc:

FastAPI redoc docs

📰 Setting up Redoc

Redoc logo

We have seen that redoc will be available for free when you create a FastAPI project.

What is happening here is that FastAPI will define a redoc url by default:

if self.openapi_url and self.redoc_url:
    async def redoc_html(req: Request) -> HTMLResponse:
        root_path = req.scope.get("root_path", "").rstrip("/")
        openapi_url = root_path + self.openapi_url
        return get_redoc_html(
            openapi_url=openapi_url, title=f"{self.title} - ReDoc"
        )

    self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
Enter fullscreen mode Exit fullscreen mode

Since both openapi_url and redoc_url has default values of /openapi.json and /redoc respectively, redoc will be served at /redoc by default.

If you want to remove redoc from your project, you just need to set redoc_url=False:

app = FastAPI(redoc_url=None)
Enter fullscreen mode Exit fullscreen mode

If you want to remove OpenAPI docs too, you just need to set openapi_url to None:

app = FastAPI(openapi_url=None)
Enter fullscreen mode Exit fullscreen mode

📃 Setting up Swagger

Swagger Logo

Swagger will work the same way as redoc. The path /docs will be added automaticaly if don't pass any value to docs_url. If you inform a new path, swagger will be served in this new url:

if self.openapi_url and self.docs_url:

    async def swagger_ui_html(req: Request) -> HTMLResponse:
        root_path = req.scope.get("root_path", "").rstrip("/")
        openapi_url = root_path + self.openapi_url
        oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
        if oauth2_redirect_url:
            oauth2_redirect_url = root_path + oauth2_redirect_url
        return get_swagger_ui_html(
            openapi_url=openapi_url,
            title=f"{self.title} - Swagger UI",
            oauth2_redirect_url=oauth2_redirect_url,
            init_oauth=self.swagger_ui_init_oauth,
            swagger_ui_parameters=self.swagger_ui_parameters,
        )

    self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
Enter fullscreen mode Exit fullscreen mode

As we can see, the setup function will add the swagger ui url to our app unless we define docs_url=None.

👋 and that's all for today. See you in the next article 🤗

Top comments (0)