Order Up! FastAPI vs Flask in the Kitchen of RESTful APIs

Tiffany Williams
7 min readOct 16, 2023

--

Split-view illustration of a high-tech kitchen with automated processes (left) contrasted with a traditional kitchen using manual methods (right). Image generated by OpenAI’s DALL·E 3

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 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.

(L) The Swagger UI homepage generated automatically by FastAPI. (M) Swagger UI’s representation of the Pydantic models used in the FastAPI application. (R) Detailed view of the 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! 🚀 👩🏽‍💻

  1. 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.
  2. 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.

--

--

Tiffany Williams

Software Engineer with a PhD in Cancer Biology. Passionate about the convergence of health & tech. Working daughter. Avid runner.