# Sample data
Source: https://speakeasy.com/blog/choosing-your-framework-python

import { CardGrid } from "@/components/card-grid";
import { pythonFrameworksData } from "@/lib/data/blog/python-frameworks";

We're fortunate to live in a time when a wide selection of Python API frameworks is available to us. But an abundance of choice can also be overwhelming. Do you go for the latest, trending option or stick with the tried-and-tested framework that offers security and control? 

Whether you're a startup founder who needs to deliver an MVP in a few weeks while taking scale and performance into consideration, or part of a large organization running hundreds of microservices needing reliable and robust technologies, choosing the right API framework is a critical decision. The key is recognizing that every framework choice involves trade-offs, which shift based on your project's unique needs. Failing to account for this can lead to frustration down the road.

In this post, we discuss the factors to consider when choosing a REST API framework and explores popular options, highlighting each framework's strengths and weaknesses. At the end of the article, we'll suggest a pragmatic approach you can take to make an informed decision.

<CardGrid cards={pythonFrameworksData} heading="Popular Python Frameworks" />

## Factors to consider when choosing a Python API framework

### Iteration speed

For startups or fast-moving teams, the pressure to ship an MVP or new features quickly can outweigh concerns about the project's long-term architecture. But this short-term focus can lead to technical debt, making it harder to scale or adapt the API later. 

To strike the right balance between speed and maintainability, it helps to understand when speed is essential and when it's worth investing time in a more robust foundation. The solution lies in using tools that offer the flexibility to write code quickly while setting aside some initial scalability or performance concerns, with the option to refactor and evolve your architecture as your project grows.

Start with a simple, script-like setup for exposing endpoints without committing to a solid architecture upfront. Once the business is stable, you can take advantage of the framework's features to transition to a more complex and robust architecture.

### Enterprise needs: Scale and security

Your MVP has succeeded, and your project now serves a significant user base. Or maybe you're operating in an enterprise environment, building a service that must handle thousands or even millions of daily requests. While flexibility is still appealing at this stage, relying on tools that prioritize flexibility over structure is no longer wise. Instead, focus on well-structured frameworks designed to help with scalability, simplify complex processes, and abstract away the challenges introduced by your growing needs.

When choosing a framework for mature or large-scale projects, you need to consider:

- **Request volume:** The number of requests your application needs to handle.
- **Authorization:** How to manage user permissions securely and efficiently.
- **Database optimization:** Ensuring database queries are performant and scalable.
- **Logging:** Implementing proper logging for monitoring and debugging.
- **Performance:** Maintaining responsiveness under heavy traffic and load.

While lightweight frameworks can handle these challenges with careful implementation, your top priorities should shift to performance, robustness, and security.

When evaluating frameworks for these needs, consider these three critical factors:

- **Framework maturity and adoption:** A framework with wide industry adoption can be a sign of reliability. A strong community and long-standing development history often reflect a framework's stability and available support.
- **Security:** A framework with many built-in features may introduce security vulnerabilities. Assess the framework's history of handling security issues, its track record with security updates, and the quality of its documentation.
- **Robustness:** Evaluate the framework's architecture for its ability to abstract complex tasks effectively, ensuring scalability and maintainability over time.

### Async support

Asynchronous programming is known for its performance benefits, especially in non-blocking operations. For example, imagine an API that handles file uploads: The user doesn't need the upload to finish immediately or receive a download link right away. They just want confirmation that the process has started and that they'll be notified of its success or failure later. This is where async frameworks shine, allowing the API to respond without waiting for the file upload to complete.

Synchronous frameworks like Flask or Django can still handle asynchronous-like tasks using background job libraries like Celery paired with tools like Redis or RabbitMQ. While these frameworks have introduced partial async support in their architectures, they are not fully asynchronous yet. Background job solutions like Celery, Redis, and RabbitMQ are robust for task delegation, but they come with additional setup complexity and don't achieve proper non-blocking behavior within the API. 

Frameworks built with async programming in mind, like Tornado and FastAPI, provide a more intuitive coding experience for async tasks.

## Popular Python API frameworks

### Flask-RESTX: Familiar, lightweight, and flexible

Flask alone is sufficient to build a REST API. However, to add important REST API features like automatic Swagger documentation, serialization, and error handling, [Flask-RESTX](https://flask-restx.readthedocs.io/) offers tools that simplify additional parts of your workflow.

Here's an example that creates an application to list payments: 

```python filename="app.py"
from flask import Flask
from flask_restx import Api, Resource, fields

app = Flask(__name__)
api = Api(app, doc="/docs")  # Swagger UI documentation available at /docs

ns = api.namespace('payments', description="Payment operations")

payment_model = api.model('Payment', {
    'id': fields.Integer(description="The unique ID of the payment", required=True),
    'amount': fields.Float(description="The amount of the payment", required=True),
    'currency': fields.String(description="The currency of the payment", required=True),
    'status': fields.String(description="The status of the payment", required=True),
})


payments = [
    {'id': 1, 'amount': 100.0, 'currency': 'USD', 'status': 'Completed'},
    {'id': 2, 'amount': 50.5, 'currency': 'EUR', 'status': 'Pending'},
    {'id': 3, 'amount': 200.75, 'currency': 'GBP', 'status': 'Failed'},
]

@ns.route('/')
class PaymentList(Resource):
    @ns.marshal_list_with(payment_model)
    def get(self):
        return payments

api.add_namespace(ns)

if __name__ == "__main__":
    app.run(debug=True)
```

This code snippet creates an application that runs on port **5000** and provides two endpoints:

- `/payments`, for listing payments.
- `/docs`, for automatically documenting the payments endpoint.

The Flask-RESTX marshaling feature is noteworthy for how it automatically maps the results – whether from a database, file, or API request – to a defined schema and sends a structured response to the client. This functionality ensures consistency and reduces boilerplate code for formatting responses. 

The Flask ecosystem gives you the flexibility to create your application in the way that suits your needs. When the time comes to scale, Flask combined with [Flask-RESTX](https://flask-restx.readthedocs.io/) provides you with the features you need to handle larger, more complex projects effectively.

### Sanic: For lightweight and production-ready real-time APIs

Sanic (not to be confused with Sonic the Hedgehog, though it's just as speedy) is a lightweight, asynchronous Python web framework designed for high-performance and real-time applications. While these characteristics might suggest complexity, writing an application that serves both an HTTP endpoint and a WebSocket server is surprisingly straightforward.

```python filename="app.py"
from sanic import Sanic
from sanic.response import json

app = Sanic("ConfigAPI")

configs = {
    "app_name": "My App",
    "version": "1.0.0",
    "debug": True,
    "max_connections": 100,
    "allowed_hosts": ["localhost", "127.0.0.1"],
}


@app.get("/configs")
async def get_configs(request):
    return json(configs)


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8000, debug=True)
```

Sanic intuitively handles static files, making it a user-friendly alternative to popular frameworks like [Django, which can require more complex configurations for similar tasks](https://www.quora.com/Why-is-Django-making-handling-static-files-so-difficult).   

```python filename="app.py"
app = Sanic("ConfigAPI")

app.static('/static', './static')
```

Another point in Sanic's favor is its interesting approach to handling TLS, a process that can be complicated to understand and set up. With Sanic, you can start your server using your certificate files, or even better, let it automatically set up local TLS certificates, enabling secure access with little configuration. 

```bash
sanic path.to.server:app \--dev \--auto-tls
```

### FastAPI: Build modern and highly typed REST APIs

FastAPI's excellent developer experience has made it one of the most popular Python frameworks. By combining async programming, type hints, and automatic OpenAPI document generation, FastAPI enables you to create highly documented APIs with minimal effort.

FastAPI's design is also async-first, making it an excellent choice for real-time APIs, high-concurrency workloads, and systems needing rapid prototyping with built-in tools. FastAPI offers modern convenience and a healthy ecosystem of complementary tooling without compromising on performance. 

The following code example demonstrates creating a REST API for listing and creating invoices.

```python filename="app.py"
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI()

class Invoice(BaseModel):
    id: int
    customer_uid: str
    amount: float
    status: str