Order Up! FastAPI vs Flask in the Kitchen of RESTful APIs
A side-by-side comparison of building APIs with FastAPI and Flask
Introduction
Building RESTful API applications is an essential part of many software projects, especially when data needs to be shared or synchronized between services. Two popular Python frameworks for this task are FastAPI and Flask. In this post, we’ll dissect these frameworks by constructing a simple restaurant ordering system.
Why this Comparison Matters
Choosing the right tool for building APIs can be challenging given the plethora of options available across different programming languages. My personal journey has taken me from Flask, a long-standing favorite in the Python community, to FastAPI, a newer entrant known for its high performance and speed. This shift wasn’t just a matter of following trends; it was driven by FastAPI’s compelling features that seemed promising for my projects. This post aims to dissect these frameworks to help you make an informed choice for your specific needs.
The Restaurant Metaphor
To bring this comparison to life, let’s employ a restaurant metaphor that illustrates the essential differences between FastAPI and Flask. In our hypothetical eateries, both FastAPI and Flask act as the kitchens, responsible for preparing and serving orders — akin to processing API requests. FastAPI is like an automated, high-tech kitchen optimized for speed, type safety, and performance. In contrast, Flask is more like a traditional kitchen where the chef (developer) has a higher degree of control but also bears more responsibilities.
Understanding FastAPI
FastAPI is built on Starlette, a lightweight asynchronous web framework for Python. Starlette provides the underlying web routing and request handling, among other features. When you use FastAPI, you’re effectively leveraging these built-in asynchronous capabilities of Starlette. For serving your application and managing connections between the server, application, and framework, FastAPI employs Uvicorn, an ASGI (Asynchronous Server Gateway Interface) server.[1].
In our restaurant metaphor, consider FastAPI as the restaurant itself. The staff — efficient and multi-tasking — represents the Starlette framework that powers FastAPI. Uvicorn, the delivery system, is akin to a fleet of delivery drivers trained to navigate multiple routes simultaneously, ensuring quick and efficient service to customers, symbolizing the application's users.
Understanding Flask
Flask, a veteran in this space, typically uses WSGI (Web Server Gateway Interface) as its server gateway[2]. For those looking to make their applications production-ready, Gunicorn frequently serves as the WSGI HTTP server, acting as an intermediary between Flask and the external world.
In a Flask-centric restaurant, the wait staff (WSGI) takes orders one at a time, awaiting each order’s preparation before moving on to the next. Gunicorn functions as a reliable but generally single-threaded delivery driver. Although Flask can support asynchronous operations, this usually involves additional setups, like integrating task queues such as Celery or Redis Queue.
Other Advantages to FastAPI
Beyond its high-speed and asynchronous capabilities, FastAPI boasts additional features like built-in type declarations and automatic model serialization. These are not inherently available in Flask. To vividly illustrate these distinctions, we will delve into example endpoints implemented in both Flask and FastAPI in the sections to follow.
What to Expect in this Post
In this example, we will create a service with an endpoint that can accept orders and return an itemized receipt. We will create an endpoint and accompanying models using FastAPI and Flask. The complete code for all examples discussed in this blog post is available on Github. Feel free to clone or fork!
Setting Up Your Development Environment
First, create a new directory named fastapi_flask_project. For dependency management and virtual environment setup, I’m using Poetry. While I won’t delve into the intricacies of Poetry, I do recommend using an isolated environment for your project. For more details on virtual environments, check out this guide.
poetry add fastapi, uvicorn, flask, marshmallow, gunicorn
Defining Data Models
FastAPI and Pydantic
# fastapi_example/models.py
from enum import Enum
from pydantic import BaseModel, Field
from typing import List, Optional
from common.types import FoodType, FoodSize, SpecialRequest
class FoodItem(BaseModel):
food_type: FoodType
food_size: FoodSize
quantity: int
class Receipt(BaseModel):
table_number: int
total_price: float
food_items: List[FoodItem]
special_requests: Optional[str]
class Order(BaseModel):
table_number: int = Field(..., description="Table number for the order")
food_items: List[FoodItem] = Field(..., description="List of food items ordered")
special_requests: List[SpecialRequest] = Field(
..., description="Any special requests for the order"
)
In FastAPI, we use Pydantic to define our request and response model, which act as the blueprints for data validation and serialization. The Order
specifies the expected incoming data format and Receipt
the outgoing data format, enabling FastAPI to validate and deserialize the data into Python objects.
Flask and Marshmallow
# flask_example/models.py
from marshmallow import Schema, fields, validate
from common.types import FoodType, FoodSize, SpecialRequest
class FoodItem(Schema):
food_type = fields.Str(
validate=validate.OneOf([e.value for e in FoodType]), required=True
)
food_size = fields.Str(
validate=validate.OneOf([e.value for e in FoodSize]), required=True
)
quantity = fields.Int(required=True)
class Recepit(Schema):
table_number: fields.Int(required=True, description="Table number for the order")
total_price: fields.Float(
validate=validate.Range(min=0),
required=True,
description="Total price of the order",
)
food_items: fields.List(
fields.Nested(FoodItem),
required=True,
description="List of food items ordered",
)
special_requests: fields.List(
fields.Nested(SpecialRequest), description="Any special requests for the order"
)
class Order(Schema):
table_number = fields.Int(required=True, description="Table number for the order")
food_items = fields.List(
fields.Nested(FoodItem),
required=True,
description="List of food items ordered",
)
special_requests = fields.Str(description="Any special requests for the order")
For Flask, we use Marshmallow to define a similar schema. Marshmallow is a widely used Python library for object serialization and deserialization. Unlike FastAPI, Flask requires an external library like Marshmallow for these functionalities. Note that Marshmallow is just one of many options for data validation in Flask; alternatives include Flask-RESTful’s reqparse, Flask-WTF, and custom validation methods.
Creating API Endpoints
FastAPI: Lean and Mean
# fastapi_example/main.py
from fastapi import FastAPI, HTTPException
from fastapi_example.models import Order, Receipt
from common.exceptions import InsufficientStockException
from common.utils import process_order
app = FastAPI()
@app.post("/order", response_model=Receipt)
async def place_order(order: Order):
try:
receipt = process_order(order.model_dump())
return {"message": "Order successfully placed", "receipt": receipt}
except InsufficientStockException as e:
raise HTTPException(status_code=400, detail=str(e))
FastAPI simplifies endpoint definition. Its built-in data validation provides implicit error handling, reducing code length and complexity. Custom error handling is also straightforward, as demonstrated by the InsufficientStockError
in our example.
You can start the FastAPI server with:
uvicorn fastapi_example.main:app --reload --port=8000
(Note: The — reload
flag enables hot reloading, which restarts your server whenever code changes.)
Flask: Flexible but Verbose
# flask_example/main.py
from marshmallow import ValidationError
from flask import Flask, request, jsonify
from common.exceptions import InsufficientStockException
from common.types import FoodType, FoodSize, SpecialRequest
from common.utils import process_order
from flask_example.models import Order
app = Flask(__name__)
@app.route("/place_order", methods=["POST"])
def place_order():
order_schema = Order()
try:
order_data = order_schema.load(request.json)
except ValidationError as e:
return jsonify({"error": e.messages}), 422
except Exception as e:
return jsonify({"message": "An unknown error occurred: " + str(e)}), 500
try:
receipt = process_order(order_data)
except InsufficientStockException as e:
return jsonify({"message": str(e)}), 400
except Exception as e:
return (
jsonify(
{"message": "An unknown error occurred during processing: " + str(e)}
),
500,
)
return jsonify({"message": "Order successfully placed", "receipt": receipt}), 200
Flask allows for greater flexibility but at the cost of more boilerplate code. Here, we manually load and validate request data and handle errors explicitly. While you could skip this step, doing so would complicate the code and potentially introduce errors.
To start the Flask server, use:
gunicorn flask_example.main:app --reload -b 127.0.0.1:8001
API Documentation: A Quick Comparison
One notable feature of FastAPI is its built-in support for Swagger, an interactive API documentation tool, based on the OpenAPI specification. This generates interactive API documentation, accessible at http://localhost:8000/docs, right out of the box.
Image 1. The Swagger UI homepage generated automatically by FastAPI.
Image 2. Swagger UI’s representation of the Pydantic models used in the FastAPI application.
Image 3. Detailed view of the place_order endpoint in Swagger UI, showcasing FastAPI’s ability to describe request parameters, expected body, and responses.
In contrast, Flask requires additional extensions and route-specific decorators to generate a similar Swagger UI page.
Final Thoughts
Both FastAPI and Flask have their merits. FastAPI offers speed and out-of-the-box features, making it ideal for projects that need to be up and running quickly. Flask offers greater flexibility but may require additional setup and extensions. Your choice between the two will ultimately depend on the specific needs of your project.
Feel free to fork the GitHub repo and experiment with the code. I’d love to hear your thoughts or see what you build, so drop a comment below. Happy coding! 🚀 👩🏽💻
ASGI (Asynchronous Server Gateway Interface) is a specification that extends WSGI to include asynchronous capabilities. It allows for greater concurrency, handling multiple requests simultaneously, making it ideal for real-time web applications.
WSGI (Web Server Gateway Interface) is a standard interface between web servers and Python web applications or frameworks. It is synchronous and processes one request at a time per thread or process, making it less suited for handling real-time web interactions.