How to change FastAPI's swagger favicon?
Tags: fastapi, python
Published: 2022-01-18 22:59:41

This is my first blog post, so I'll be succinct. This is more of a step-by-step tutorial, but if you're only interested in the code, feel free to [check it](https://github.com/Kludex/blog/blob/main/fastapi_favicon/README.md).

Let's start defining our goal: **we want to change our Swagger UI favicon using a local image**.

### Step 1: Create our FastAPI application

Let's create a simple FastAPI application with a single endpoint.

```python
# main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def home():
    ...
```

Let's run our [`uvicorn`](https://www.uvicorn.org/) server: `python -m uvicorn main:app --reload`, and use a HTTP client to see what we get from it. I'll be using [`httpie`](https://httpie.io/), but feel free to use `curl` or any other HTTP client of your choice.

> Note that we've used `--reload` flag on the uvicorn command. To know more about the CLI options, check [`uvicorn` documentation](https://www.uvicorn.org/deployment/#running-from-the-command-line).

```bash
❯ http :8000
HTTP/1.1 200 OK
content-length: 4
content-type: application/json
date: Tue, 18 Jan 2022 22:05:38 GMT
server: uvicorn

null

```

Now that we're sure that our application works, let's go to the next step.

### Step 2: Replace the default docs page

Following the [ycd](https://github.com/ycd) snippet on his [comment](https://github.com/tiangolo/fastapi/issues/2581#issuecomment-752398688), we'd need to replace the default swagger endpoint. The problem with this approach is that we lose internal logic, as the [original endpoint does more](https://github.com/tiangolo/fastapi/blob/f0388915a8b1cd9f3ae2259bace234ac6249c51a/fastapi/applications.py#L168-L180) than what is in there.

In any case, let's start adding the snippet mentioned above.

```python
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html


app = FastAPI(docs_url=None)


@app.get("/docs", include_in_schema=False)
async def swagger_ui_html():
	return get_swagger_ui_html(
        openapi_url="/openapi.json",
        title="FastAPI",
        swagger_favicon_url="https://placekitten.com/200/300"
    )


@app.get("/")
async def home():
    ...
```

We still have `uvicorn` running, and as the reload flag is set, we can see the changes. Go to your browser, and access http://127.0.0.1:8000/docs#/.

As you can see, the favicon has changed.

### Step 3: Serve the local favicon

FastAPI is able to serve files from a given directory thanks to Starlette's [`StaticFiles` class](https://www.starlette.io/staticfiles/), and that's exactly what we're going to do.

Let's create a directory with our favicon, and serve that image.

```python
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.staticfiles import StaticFiles


app = FastAPI(docs_url=None)
app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/docs", include_in_schema=False)
async def swagger_ui_html():
	return get_swagger_ui_html(
        openapi_url="/openapi.json",
        title="FastAPI",
        swagger_favicon_url="/static/favicon.png"
    )


@app.get("/")
async def home():
    ...
```

At this point, we need to create a folder called "static", or any other name, just remember to change the name on the `directory` argument on the `StaticFiles` instantiation. On this folder, we'll add our image (on our case it's going to be a `favicon.png`).

Pay attention on the snippet above that we've changed the value of `swagger_favicon_url` on the `get_swagger_ui_html` function. If you access the docs page now, you'll see the favicon.

Our goal has been accomplished, but that's not quite the end of it.

### Step 4: Improve with details

As I've said at the beginning, the internal FastAPI docs endpoint has more logic than what we've used. So let's add those to our solution.

```python
from fastapi import FastAPI, Request
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI(docs_url=None, redoc_url=None)
app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/docs", include_in_schema=False)
async def swagger_ui_html(req: Request) -> HTMLResponse:
    root_path = req.scope.get("root_path", "").rstrip("/")
    openapi_url = root_path + app.openapi_url
    oauth2_redirect_url = app.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=app.title + " - Swagger UI",
        oauth2_redirect_url=oauth2_redirect_url,
        init_oauth=app.swagger_ui_init_oauth,
        swagger_favicon_url="/static/favicon.png",
        swagger_ui_parameters=app.swagger_ui_parameters,
    )


@app.get("/")
def home():
    return RedirectResponse("/docs")
```

There are two things to notice on the above improvements: the body of the `swagger_ui_html` endpoint function has changed, and the `/` endpoint has been changed to redirect to the `/docs` endpoint. On the first, we're concerned about the OAuth2 integration with the Swagger UI, and on the second is just to show you something different. :)

That's it.

Thanks for reading until here, and please, give me some feedback and ideas. I'm planning to write daily posts.