FastAPI is often touted for its speed, but its real magic lies in how it leverages Python’s type hints to provide both incredible performance and developer ergonomics simultaneously.
Let’s see it in action. Imagine a simple Flask app that doubles a number:
# flask_app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/double', methods=['POST'])
def double_number():
data = request.get_json()
number = data.get('number')
if not isinstance(number, (int, float)):
return jsonify({"error": "Invalid input, 'number' must be an integer or float"}), 400
return jsonify({"result": number * 2})
if __name__ == '__main__':
app.run(debug=True)
Now, let’s migrate this to FastAPI. The core idea is to define data models using Pydantic, which FastAPI uses under the hood.
# fastapi_app.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class NumberInput(BaseModel):
number: float # Pydantic automatically handles int/float coercion
@app.post('/double')
def double_number(item: NumberInput):
return {"result": item.number * 2}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Notice how NumberInput with number: float automatically handles both integers and floats, and FastAPI provides automatic data validation and OpenAPI documentation (Swagger UI at /docs).
The performance difference comes from two main sources: Starlette (the ASGI framework FastAPI is built on) and Pydantic. Starlette is designed for asynchronous operations, allowing FastAPI to handle many requests concurrently without blocking. Pydantic’s data validation, while adding a step, is highly optimized and often faster than manual checks in Python, especially at scale. More importantly, Pydantic’s type hints enable FastAPI to generate precise JSON schemas, which are then used by tools like uvicorn and hypercorn to serialize/deserialize data extremely efficiently, often bypassing Python’s slower json module for internal operations.
When you run the FastAPI app with uvicorn main:app --reload, you get a high-performance web server. The uvicorn server, coupled with Starlette’s ASGI capabilities, means that I/O-bound operations (like waiting for network responses) don’t block the entire application. Instead, the server can switch to handling other requests while waiting, leading to significantly higher throughput compared to the traditional WSGI model used by Flask. This is especially beneficial for APIs that perform external calls or database queries.
The key lever you control in FastAPI is how you define your request and response models using Pydantic. Every field in a Pydantic model is a type hint, and Pydantic uses these hints for parsing, validation, and serialization. For example, if you have a field user_id: int, FastAPI will automatically ensure that any incoming data for user_id is converted to an integer, and it will raise a 422 Unprocessable Entity error if it cannot be converted or if the field is missing (unless you specify Optional). This level of automatic validation drastically reduces boilerplate code and potential runtime errors.
One of the most surprising benefits of using Pydantic models with FastAPI is how it influences your database interactions. While Pydantic is primarily for API request/response bodies, its models can serve as excellent ORM-like structures for data validation and serialization before saving to or after retrieving from a database. You can even use pydantic.v1.parse_obj_as or pydantic.parse_obj_as (depending on your Pydantic version) to convert database query results (often dictionaries or tuples) into strongly typed Pydantic objects, providing a consistent interface and reducing the need for manual data wrangling between your database layer and your API endpoints. This means you can define a single source of truth for your data structure, used for both API contracts and internal data representation.
The next logical step in optimizing your FastAPI application involves exploring dependency injection.