FastAPI - The Good, the bad and the ugly.
Tags: python, fastapi, flask, django
Published: 2020-08-04 12:30:55

FastAPI is a relatively new web framework for Python claiming to be one of the fastest Python frameworks available. In this article, I will discuss the pros and cons of the framework during my short experience with it. I will also include some examples and solutions to minimize the cons.

#**The Good**
### 1. It is indeed **Fast**API
`FastAPI` is properly fast when we compare it to other major Python frameworks like `Flask` and `Django`. The following score chart from [Techempower] (https://www.techempower.com/benchmarks/) shows how much difference in performance there is between the frameworks.

![FastAPI vs Flask vs Django](https://dev-to-uploads.s3.amazonaws.com/i/7yxcdbt72bwrj0cwm89y.PNG)

I also did a small test myself to see which framework is the fastest, and the results are actually quite interesting. For this test, I set up a basic "Hello world" API for all 3 frameworks. I tested the response times by calling the APIs and took the average time of response. The results can be divided into two scenarios:

**a.**  Average time for the first call after server start up
**b.**  Average time for consecutive calls after the first call

`Django` and `FastAPI` respond slower than usual in their first API call. `Flask` always stays consistent but is much slower than the other two during all API calls.
The average time taken by all 3 APIs are shown below:


| Framework     |     Case a     |   Case b   | 
| ------------- |:--------------:| ----------:|
| FastAPI       |     17 ms      |    6.2 ms  |
| Django        |    517.2 ms    |   5.834 ms |
| Flask         |    507.2 ms    |   508.9 ms |

One interesting thing to notice here is that Django actually performs a little faster than FastAPI after the first call. But in certain scenarios like a serverless environment, Django's high first call and startup time might become an issue. It is to be noted that the measurements were done with little data, on a particular environment and my experience with Flask and Django is very limited, so the results may vary for you. 

### 2. Support for *asynchronous* code
The most exciting feature of FastAPI is that it supports asynchronous code out of the box using the `async/await` Python keywords. Here is an example of an API that fetches data from Reddit asynchronously. (Example reference: [Python async/await Tutorial by Scott Robinson](https://stackabuse.com/python-async-await-tutorial/))

```python
app = FastAPI()

async def get_json(client: ClientSession, url: str) -> bytes:
    async with client.get(url) as response:
        assert response.status == 200
        return await response.read()

async def get_reddit_top(subreddit: str, client: ClientSession, data: dict):
    data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')

    j = json.loads(data1.decode('utf-8'))
    subreddit_data = []
    for i in j['data']['children']:
        score = i['data']['score']
        title = i['data']['title']
        link = i['data']['url']
        print(str(score) + ': ' + title + ' (' + link + ')')
        subreddit_data.append(str(score) + ': ' + title + ' (' + link + ')')
    data[subreddit] = subreddit_data
    print('DONE:', subreddit + '\n')


@app.get("/")
async def get_reddit_data_api() -> dict:
    start_time: float = time.time()
    client: ClientSession = aiohttp.ClientSession()
    data: dict = {}

    await asyncio.gather(
        get_reddit_top('python', client, data),
        get_reddit_top('programming', client, data),
        get_reddit_top('compsci', client, data),
    )
    await client.close()

    print("Got reddit data in ---" + str(time.time() - start_time) + "seconds ---")
    return data
```
The magic of asynchronous code is, because of the coroutines `get_reddit_top` running concurrently, the execution time of the API has been reduced significantly compared to the execution time if run serially.

### 3. Very short development time
To create a basic "Hello world" API, the frameworks require the following number of lines of code (considering the whole project):

| Framework     | Lines of Code  |
| ------------- |:--------------:| 
| FastAPI       |     8 lines    |  
| Flask         |     7 lines    |  

I have not considered Django as I think it has a different structure than the other two.
If you want to scale your FastApi app, the effort is also similar to Flask. Both have concept of modularity via Blueprint in Flask and Router in FastAPI. So, I would say Flask and FastAPI have very similar development times. 

### 4. Easy testing
Testing FastAPI endpoints are really straight forward and can be done using [TestClient](https://fastapi.tiangolo.com/tutorial/testing) provided by FastAPI. This makes Test Driven Development(TDD) very easy.

```python
app = FastAPI()

@app.get("/")
async def read_main():
    return {"msg": "Hello World"}

client = TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}
```

You can easily mock the service calls or code inside your API definition functions like `read_main` and test it using TestClient.

### 5. Seamless central exception handling
To do exception handling in FastAPI, you simply have to use `@app.exception_handler` annotation or `app.add_exception_handler` function to register the response for an `Exception` and it will be handled by FastAPI.

```python
app = FastAPI()

@app.exception_handler(SomeException)
async def http_exception_handler(request: Request, exc: SomeException) -> PlainTextResponse:
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)

async def request_exception_handler(request: Request, exc: SomeOtherException) -> PlainTextResponse: 
return PlainTextResponse(str(exc.detail),status_code=exc.status_code)

app.add_exception_handler(exc_class_or_status_code=SomeOtherException,
handler=request_exception_handler)
```
### 6. Excellent documentation
FastAPI has a very extensive and example rich [documentation](https://fastapi.tiangolo.com/), which makes things easier. If you need to look up something about FastAPI, you usually don't have to look elsewhere.


### 7. Easy deployment
You can easily deploy your FastAPI app via Docker using FastAPI provided [docker image](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker). You can also deploy it to AWS Lamdba using [Mangum] (https://pypi.org/project/mangum/).

#**The Bad**
### 1. Crowded main file

In FastAPI, everything is tied to the `FastAPI app`. So, your `main.py` file can very easily become very crowded. Here is an example.

```python
app = FastAPI()

app.include_router(users.router)
app.include_router(items.router)
app.include_router(shops.router)
app.include_router(other.router)

@app.exception_handler(SomeException)
async def http_exception_handler(request: Request, exc: SomeException) -> PlainTextResponse:
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)

@app.exception_handler(SomeOtherException)
async def http_exception_handler(request: Request, exc: SomeOtherException) -> PlainTextResponse:
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
```
Now imagine you have 10 routers and 20 exceptions to handle, the `main.py` file then can become very difficult to maintain. Luckily, this can easily be solved.

```python
app = FastAPI()

include_routers(app);
add_exception_handlers(app);
```

`include_routers` and `add_exception_handlers` can be kept in separate files.

### 2. No singleton in Dependency Injection
Dependency Injection in FastAPI does no support `singleton` instances, according to this [Github thread](https://github.com/tiangolo/fastapi/issues/504), but it supports single instance for each HTTP request. You either have to create singleton classes yourself or use a different DI library.

#**The Ugly**
### Request Validation

My worst experience while working with FastAPI was handling request validations. It uses validation from `Pydantic`, and there is, to my knowledge, no straight forward way to pass down a validation message from the point of validation to the response. You have make do with whatever message is passed down by `Pydantic` through `RequestValidationError` or write a custom validator. For example,

```python
app = FastAPI()

class SomeDto(BaseModel):
    data: str = Field(min_length=1, description="Minimum length must be greater than 1",
                      title="Minimum length must be greater than 1")

@app.post(path="/")
async def get_response(request: SomeDto):
    return "some response"

@app.exception_handler(RequestValidationError)
async def handle_error(request: Request, exc: RequestValidationError) -> PlainTextResponse:
    return PlainTextResponse(str(exc.errors()), status_code=400)
```

`exc.errors()` returns a list of validation violations with hard coded messages from `Pydantic`. I have found no way to change it from documentations of both `FastAPI` and `Pydantic`. Even the `description` and `title` parameter values are lost.

#**Getting started**
If you want get started with FastAPI, there are some very good resources out there. Here are some of them that you can explore:

*  [First Steps - FastAPI](https://fastapi.tiangolo.com/tutorial/first-steps/) - The documentation of FastAPI itself


*  [High-performing Apps with Python – A FastAPI Tutorial](https://www.toptal.com/python/build-high-performing-apps-with-the-python-fastapi-framework) by *Zubair Ahmed* - A very detailed tutorial on how to build secure Rest APIs with database communication and authentication.

*  [Deploying and Hosting a Machine Learning Model with FastAPI and Heroku] (https://testdriven.io/blog/fastapi-machine-learning/) by *Michael Herman* - A detailed tutorial on how to serve ML models using FastAPI from creation to deployment, with open source example in *GitHub*.

In conclusion, FastAPI is a fast web framework with support for asynchronous code and has very good documentation. The pros of FastAPI outweighs the cons by a great margin, and I highly recommend you check it out.