Choosing a framework is not easy, and that’s why I’m here to help you get rid of the headache.
Why should we even compare Flask and FastAPI?
They are similar. Both are stripped-down Python microframeworks without the bloated bells and whistles, which means faster development time and more flexibility. Also, both are used for building APIs and web applications.
They are also different. Flask is more battle-tested, therefore slightly more reliable, and it’s widely used. FastAPI is a newer, more modern framework known for its speed with lots of built-in support like Pydantic and SwaggerUI.
Now that you have a better understanding of each framework, let our faceoff begin!
Installation
Sometimes the most challenging part of learning something new is actually getting started. That’s why we’ll start with Installation.
It’s relatively straightforward to install both Flask and FastAPI using Python’s favorite installer, pip. It’s also good practice to install both inside a virtual environment, an isolated environment for each of your Python projects that eliminates collision errors.
Flask
$ pip install flask
FastAPI
$ pip install fastapi uvicorn
Conclusion: Notice that you install FastAPI with Uvicorn. Think of Uvicorn as a lightning-fast server that allows your applications to perform faster.
Hello World Application
If you’ve only written one line of code in your entire life, I bet it was something like this:
print(“Hello World”)
It’s kind of like if you were learning another language, let’s say Mandarin. There’s a system called Pinyin, which transcribes Chinese characters to English so people can pronounce them. It’s designed to get you up and running quickly, just like a Hello World application.
Let’s see what a hello world application looks like in both Flask and FastAPI.
Flask < 2.0
# inside of a Python .py file
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=\[“GET”])
def home():
return {"Hello": "World"}
if __name__ == "__main__":
app.run()
Flask 2.0
from flask import Flask
app = Flask(__name__)
@app.get("/")
def home():
return {"Hello": "World"}
if __name__ == "__main__":
app.run()
FastAPI
# inside of a Python .py file
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {"Hello": "World"}
if __name__ == "__main__":
uvicorn.run("main:app")
Conclusion: In the newer versions of Flask, you can use the @app.get()
and @app.post()
decorators as shortcuts for routing. The previous way of using @app.route()
required you to pass in your HTTP verbs to a methods list like so: methods=\[“GET”, “POST”]
.
Note: Flask does a
GET
by default, so you don’t need to specify it in the methods list.
These methods also come in FastAPI with support for the following decorated routes for each HTTP method:
@app.get()
@app.post()
@app.put()
@app.delete()
Running in Development
Once you have your "Hello World" app written, you’ll want to run it in development—or locally on your machine— first, before putting it out into production for the whole world to see. If your application doesn’t work as expected, people will definitely freak out. You want to minimize the freakout.
So in your terminal, run these commands:
Flask
$ export FLASK_APP=app.py
$ export FLASK_ENV=development
$ flask run
FastAPI
$ uvicorn main:app --reload
Conclusion: FastAPI uses Hot Reloading, which keeps the app running while you’re making code changes. Hence, you don’t have to keep restarting the development server. With Flask, you need an extra terminal command: export FLASK_ENV=development
, which allows you to make code changes without restarting your development server.
HTTP Methods
In the Hello World example, we saw what a GET looks like in Flask and FastAPI, so now let’s take a closer look at a POST method.
Flask < 2.0
@app.route("/teams", methods=["POST"])
def create_team():
team = {
"team_name": "Phoenix Suns",
"players": [
{
"name": "Chris Paul",
"age": 36
}
]
}
teams.append(team)
return (jsonify(teams))
Flask 2.0
@app.post("/teams")
def create_team():
team = {
"team_name": "Phoenix Suns",
"players": [
{
"name": "Chris Paul",
"age": 36
}
]
}
teams.append(team)
return (jsonify(teams))
FastAPI
@app.post("/teams")
def create_team():
team = {
"team_name": "Phoenix Suns",
"players": [
{
"name": "Chris Paul",
"age": 36
}
]
}
teams.append(team)
return {'teams':teams}
Conclusion: Flask 2.0 and FastAPI look very similar when doing a POST method. The trick is seeing how new data is created.
With Flask, you’ll have to use a tool like Postman acting as a client, so you can see your POST requests and the data you’ve created in JSON format.
FastAPI comes with Pydantic and SwaggerUI out of the box, which allows you to use automatic documentation to interact with your requests from the browser, including POST requests.
Flask can also use automatic documentation, but you’ll have to install it using flask-swagger. There’s also lots of configuration involved to make it work. Let’s look at how to see your POST requests in FastAPI in the next section.
Automatic Documentation
If you believe in magic, you’ll most definitely love Automatic Documentation.
FastAPI is based on Pydantic, a framework for easily modeling and validating objects. It comes out of the box, so no need to install it. Pydantic takes the pain of writing constructors away, and you get all the magic methods. Pydantic also does Data validation which displays friendlier errors and uses python type hints, reducing debugging time. To access your automatic documentation, make sure your development server is running, then go to your localhost and the port on which your application is running:
http://127.0.0.1:8000/docs
You’ll see your POST request like the example below; if you’re using other HTTP methods, these will be visible as well.
Let’s do something much cooler so we can see the beauty of automatic documentation. Let’s say we have this code in FastAPI:
FastAPI
from pydantic import BaseModel
app = FastAPI()
class Player(BaseModel):
player_name: str
player_team: str
player_age: int
@app.post("/teams")
def create_team(request: Player):
return {'teams':request}
Notice that in order to use Pydantic, you have to import the BaseModel
that the Player
class will inherit. We’re also declaring variables as type hints inside our class and returning a dictionary in our POST request.
When you pull up your automatic documentation, you’ll see a Schema. This Schema is a skeleton for your model with variables, where you can see which fields are required and which are optional.
You can also “Try it out” and test your API endpoint by passing in values for the variables. Here, for example, we’re passing in “Michael Jordan”
for the variable player_name
of type String.
Then, when you click Execute, it’ll give you the Response Body. There’s no need to use an extra tool like Postman.
Your interactive documentation will also generate a curl command for you, so you don’t have to write one from scratch:
Conclusion: Since Automatic Documentation comes out of the box with FastAPI along with Pydantic and Swagger UI, these features will definitely speed up your development time. You don’t have to install any external tools to test your requests.
Data Validation
Since our lovely friend Pydantic comes with FastAPI upon installation, it will give you some pretty friendly error messages when you run into problems with your code.
FastAPI
from pydantic import BaseModel
from typing import Optional
class Login(BaseModel):
username: str
password: str
agree_to_terms: Optional\[bool]
@app.post("/login")
def login(request: Login):
if request.username == "janedoe" and request.password == "password12345":
return {"message": "Success"}
return {"message": "Authentication Failed"}
Here we’re creating a class Login that inherits from the Pydantic BaseModel with type hinted variables inside of it. We are first checking if the username
is janedoe
and the password
is passworld12345
, then we return a success or a failure message accordingly.
We turn to automatic documentation and test our request body by passing in None
to the username:
Pydantic will work its magic, and you’ll get a friendly message telling you exactly what the error is. In this case, it returns the error Expecting Value
, which is right on the money because we passed in None
to the username
.
Conclusion: Flask does not have any in-house data validation support. You can use the powerful Pydantic package for data validation by installing it with Flask-Pydantic.
URL or Path Parameters
A path or URL parameter fetches one single item. Let’s say we want to get a single player. Whichever player has an id of what we pass into the URL will be returned to the user.
Let’s say we have a list of dictionaries, and we want to get one player from this JSON file:
players = [
{
"player_id": 1,
"name": "Giannis"
},
{
"player_id": 2,
"name": "Luka"
}
]
Flask
@app.get('/players/<int:player_id>')
def get_player_details(player_id):
for player in players:
if player["player_id"] == player_id:
return jsonify(player)
</int:player_id>
Here we pass in our route to localhost on port 5000 with an id of 2, and we get back the player with an id of 2.
FastAPI
@app.get("/player/{player_id}")
def get_player_details(player_id: int):
for player in players:
if player['player_id'] == player_id:
return {'player':player['name']}
Here we pass in our route to localhost on port 8000 with an id of 1, and we get back the player with an id of 1.
Conclusion: With FastAPI, since it’s using Python type hinting, you can port your code to other frameworks, like Django. With Flask, it’s not portable because we’re using framework-specific type hinting, not Python hinting.
Templates Folder
The Templates Folder stores your HTML files when you’re building a web application in Flask or FastAPI, and you have to use Jinja to display your variables in HTML. Jinja is a templating engine that allows you to write code similar to Python to display HTML.
Flask
By default, Flask looks for templates in a "templates" folder. You’ll just need to create one in your file structure.
Then you can use Jinja to display your variables by surrounding them with double curly braces:
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Players</title>
<h1>Display Players</h1>
{{ player.name }}
{{ player.jersey_number }}
Conclusion: Jijnja comes with Flask when installed, which is a huge plus. In FastAPI, you have to install Jinja and define the templates folder in your code.
Production Server
At some point, you’ll want to deploy your application and show it to the world.
Flask
Flask uses a web server called WSGI, which stands for Web Server Gateway Interface and has been the Python standard for many years. The drawback is that it’s synchronous. This means that if you have a bunch of requests, they have to wait in line for the queue to complete.
FastAPI
FastAPI uses a web server called ASGI or Asynchronous Server Gateway Interface, which is lighting fast because it’s—well, you guessed it—Asynchronous. So, if you have a bunch of requests coming in, they don’t have to wait for the other ones to complete before they are processed.
Conclusion: ASGI makes for faster performance in your web applications because they process requests asynchronously.
Drumroll, please.
The winner is... well, it depends.
This is how you can choose.
Use Flask if you want:
A battle-tested framework, as it’s been around for a long time
To develop a quick prototype
To do web application development
Use FastAPI if you want:
Speed, as in development time and performance
To decrease the number of bugs and errors in your code
To build APIs from scratch
Ok, so you’ve seen both Flask and FastAPI in action. Now you have a better understanding of both, and you've figured out which one would be a better fit for your next project.
So which framework did you choose? Tweet us @VonageDev or @tonyasims.