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 FastAPI
FastAPI
is properly fast when we compare it to other major Python frameworks like Flask
and Django
. The following score chart from Techempower shows how much difference in performance there is between the frameworks.
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)
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 provided by FastAPI. This makes Test Driven Development(TDD) very easy.
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.
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, 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. You can also deploy it to AWS Lamdba using 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.
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.
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, 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,
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 - The documentation of FastAPI itself
High-performing Apps with Python – A FastAPI Tutorial 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 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.
Top comments (15)
@fuadrafid
Actually to pass validation error message you can just use pydantic
@validator
decorator. For example:Then on validation error this will be the response body:
Thank you for reading!
You are suggesting custom validators, I was talking about the default validators provided by pydantic. These work fine, but image having a project with 50+ dataclasses, writing validators for each of the variables isn't really efficient.
You can see Springboot's validators. A simple message inside the annotation itself, simple and efficient.
Thanks. It's a good read. As mentioned in Cons 2, async/await like Node.js style could be a big advantage in case that the web app is not CPU bounded and needs to deal with a lot of requests dispatching them to backend servers, database and APIs. I am on it.
Hi ! good thoughts
About your last issue..
I'm working on the project django-ninja ( github.com/vitalik/django-ninja ) which also uses Pydantic for validation
And also think on some ways to give users a way to customize validation messages...
Do you have any design in mind ? like how would you prefer to customize validation message and on which layer ?
Hi,
Thank for sharing your work. I think the validation can be incorporated with the dtos with an annotation and validation checks should be at the middleware layer.
I am having the same problem with validations with FastAPI.
Django Rest Framework is doing a great job with this, example:
Did you found any solution for better formating of validation errors in FastAPI?
Hello Mirza,
I haven't really looked into it. But you can look into this GitHub thread:
github.com/tiangolo/fastapi/issues...
Like Takashi Futada commented here—I think the async/await Python keywords is a huge plus for FastAPI, no need for a third-party framework to do asynchronous. My colleague wrote a comparison of the two frameworks that you might find interesting: stxnext.com/blog/fastapi-vs-flask-...
Nice post. I have recently started using FastAPI. I think it will be much better when a stable version of it will be released. Waiting for version 1.0.0 actually. I have high hopes for FastAPI.
Thanks. The future of FastAPI looks good to me too. Happy coding!
[Advertisement warning] regarding con point 2 I wrote a dependency injection framework which works well with fastapi and addresses this point: github.com/meadsteve/lagom
Thanks for sharing your work! It seems feature rich and easy to use. Will try it out in the future.
After all, the performance is a very high value for fastapi... though i am mastered in django, i am learning fastapi instead of flask. I think FastAPI will be better as time goes.
Thank you for reading! Hope it gets better with time.
Although the documentation is good but the resources available for integrating it with other services are very few. Hopefully it will get better with time.
Btw, nice article! :)