Oct 23, 20268 min read

FastAPI vs Django vs Flask

How the three most used Python web frameworks compare on async, validation, data access and project structure, and what each leaves for you to set up before production.

FastAPI, Django and Flask cover most Python web backends, and they sit at different points on the same line, with Flask giving you a small synchronous core, Django giving you a full application with an ORM and admin, and FastAPI giving you an async API layer built on type hints. The consequences of the choice tend to show up later rather than on day one, for example when a Flask app has grown its own validation and structure by hand, when a Django project needs async endpoints that its sync-first extensions do not support, or when a FastAPI codebase has assembled an ORM, migrations and auth from separate libraries that nobody chose together.

The main differences:

  • FastAPI is ASGI and async-first, Flask is WSGI and synchronous by default, and Django supports both with a sync-first ecosystem.
  • FastAPI validates requests from type hints, Django validates through forms or serializers, and Flask leaves validation to extensions.
  • Django includes an ORM, migrations, admin and auth, while FastAPI and Flask leave data access and auth to you.
  • None of the three provision a database, queue or bucket, locally or in production.

FastAPI vs Django vs Flask at a glance

OptionApproachBest fit
FastAPIAsync API framework built on Starlette and PydanticNew JSON APIs and ML or AI services
DjangoFull-stack framework with ORM, admin and authRelational, CRUD-heavy apps
FlaskMinimal WSGI microframeworkSmall services and internal tools
Compare features across all three frameworks
CapabilityFastAPIDjangoFlask
RoleAPI frameworkApplication frameworkMicroframework
InterfaceASGIWSGI and ASGIWSGI
ValidationPydantic from type hintsForms, DRF serializers or django-ninjaExtensions
ORM and migrationsBring your own (SQLAlchemy, SQLModel)Django ORM and migrationsBring your own (Flask-SQLAlchemy, Alembic)
Admin UINoYesVia extensions
OpenAPIGeneratedVia DRF or django-ninjaVia extensions
Background workBackgroundTasks in processBring your own queueBring your own queue
Local infrastructureYou run itYou run itYou run it
Production infrastructureYou provision itYou provision itYou provision it

Encore is left out of these tables because it is not a fourth framework. It is an infrastructure SDK that works with any Python framework, so the section below shows it added to FastAPI, Django and Flask.

Encore: an infrastructure SDK for the framework you choose

Encore

Encore combines your choice of framework with infrastructure declared in Python, as an SDK you install into the existing app, so FastAPI, Django, Flask or any other framework keeps routing and request handling while Postgres databases, Pub/Sub topics, object storage buckets, Redis, cron jobs and secrets are module-level declarations that Encore provisions. encore run starts the databases, topics and buckets locally next to the app, and a deploy creates the same resources in your own AWS or GCP account.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.

With FastAPI, the database and topic are declarations at the top of the module and the handler queries and publishes through them:

from encore import pubsub from encore.storage import postgres from fastapi import FastAPI from pydantic import BaseModel db = postgres.Database("users", migrations="./migrations") signups = pubsub.Topic("signups") app = FastAPI() class Signup(BaseModel): email: str @app.post("/users", status_code=201) async def create_user(req: Signup) -> dict: row = await db.query( "INSERT INTO users (email) VALUES ($1) RETURNING id", req.email ).one() await signups.publish({"user_id": row["id"], "email": req.email}) return {"id": row["id"]}

With Django, the ORM, admin and sessions stay as they are and the standard PostgreSQL backend connects to the database Encore provisions, using its connection string in settings.py:

# settings.py; storage/resources.py declares db = postgres.Database("django") from psycopg.conninfo import conninfo_to_dict from storage.resources import db connection = conninfo_to_dict(db.connection_string) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": connection.pop("dbname"), "USER": connection.pop("user", ""), "PASSWORD": connection.pop("password", ""), "HOST": connection.pop("host", ""), "PORT": connection.pop("port", ""), "OPTIONS": connection, } }

With Flask, which has no lifespan hook to attach to, the SDK works the framework-agnostic way its README describes: the declarations sit at module scope, importing the SDK initializes the Encore runtime when the app runs through Encore, and synchronous views call the async API through asgiref.sync.async_to_sync, the same bridge Encore uses in its Django example. The app binds to the address Encore passes in ENCORE_LISTEN_ADDR, and an encore.service.json next to it declares the service:

import os from asgiref.sync import async_to_sync from encore.storage import postgres from flask import Flask, jsonify db = postgres.Database("users", migrations="./migrations") app = Flask(__name__) async def find_user(user_id: int): return await db.query("SELECT id, email FROM users WHERE id = $1", user_id).first() @app.get("/users/<int:user_id>") def get_user(user_id: int): row = async_to_sync(find_user)(user_id) if row is None: return jsonify(error="not found"), 404 return jsonify(row) if __name__ == "__main__": host, _, port = os.environ.get("ENCORE_LISTEN_ADDR", "127.0.0.1:8000").rpartition(":") app.run(host=host, port=int(port))

Background work that would otherwise need a separate queue becomes a subscription with its retry policy in the declaration, and a cron job declaration replaces a scheduler process by having Encore send a POST to an endpoint on a schedule.

For an AI coding agent, a new queue is then a few lines of Python in the same pull request as the logic, which it can run locally right away instead of waiting for someone to create it in a cloud console.

Key features

  • Postgres, Pub/Sub, object storage, Redis, cron jobs and secrets are declared in Python next to the code that uses them.
  • encore run starts the declared infrastructure locally, and encore test runs tests with Encore-managed resources.
  • Subscriptions are async def handlers that receive a typed Message, with retries, backoff and concurrency configured in the declaration.
  • A deploy provisions the declarations in your own AWS or GCP account.

Good to know

Encore works with any Python framework, with FastAPI, Django and Flask as the examples here. The SDK API is async, so synchronous Django and Flask views call it through asgiref.sync.async_to_sync, and subscriptions start when encore.start() is awaited on the app's event loop, which in FastAPI fits in the lifespan handler. Python support is in beta, and you can sign up for access.

Try Encore →

FastAPI: async APIs from type hints

FastAPI

FastAPI reads path, query and body parameters from the function signature, validates them with Pydantic and generates an OpenAPI schema with interactive docs.

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Signup(BaseModel): email: str @app.post("/users", status_code=201) async def create_user(req: Signup) -> dict: return {"email": req.email}

Key features

  • Validation, serialization and the OpenAPI schema come from the same type hints.
  • Depends provides dependency injection for auth, sessions and shared logic.
  • Async handlers can await database and network calls without blocking the worker.

Good to know

FastAPI does not include an ORM, migrations, auth or a project layout, so a growing codebase depends on conventions the team sets, and BackgroundTasks runs in the request process, which makes it unsuitable for work that has to survive a restart.

Try FastAPI →

Django: the full application framework

Django

Django includes an ORM, migrations, admin, authentication, forms and templating, and APIs are usually built with Django REST framework or django-ninja on top.

# views.py, using django-ninja for typed request bodies from ninja import NinjaAPI, Schema api = NinjaAPI() class Signup(Schema): email: str @api.post("/users") def create_user(request, req: Signup): return {"email": req.email}

Key features

  • Models generate migrations and an admin UI.
  • Users, sessions, permissions and CSRF protection are built in.
  • The ecosystem of reusable apps is the largest of the three.

Good to know

Django supports async views and parts of the ORM run async, but many third-party apps are still synchronous, and its conventions add weight when the service only needs a handful of JSON endpoints.

Try Django →

Flask: a small core with extensions

Flask

Flask provides routing, request and response objects and templating, and leaves validation, data access and auth to extensions.

from flask import Flask, request app = Flask(__name__) @app.post("/users") def create_user(): data = request.get_json() return {"email": data["email"]}, 201

Key features

  • The core is small and easy to read end to end.
  • Blueprints split an app into modules.
  • Flask-SQLAlchemy, Flask-Login and Flask-Migrate cover common needs.

Good to know

Flask is WSGI and synchronous by default, and its async views run each coroutine in an event loop on the worker thread handling the request, which does not give the concurrency of an ASGI server, so I/O-heavy APIs with many concurrent requests are a better fit for FastAPI.

Try Flask →

Performance and concurrency

FastAPI runs on an ASGI server such as Uvicorn, so an async handler that awaits a database query frees the worker for other requests in the meantime. Flask and synchronous Django views block a worker thread for the duration of each request, and they scale by adding threads or processes behind Gunicorn. In practice the database and external calls dominate response times for most APIs, so the concurrency model matters most for services with many slow I/O calls, such as ones that call LLM APIs.

How to choose

  • Encore added to whichever framework you pick, such as FastAPI or Django, when you want the database, Pub/Sub, buckets and cron jobs declared in Python and provisioned locally and in your AWS or GCP account.
  • FastAPI for new JSON APIs and async, I/O-heavy services.
  • Django for relational apps that need an admin, auth and an ORM from the start.
  • Flask for small synchronous services and internal tools.

Python support in Encore is in beta, and you can sign up for access if you want to try it with your FastAPI or Django app.

Frequently asked questions

Should I use FastAPI, Django or Flask?

Use FastAPI for new JSON APIs that benefit from async handlers and type-hint validation, Django for relational apps that want an ORM, migrations, admin and auth included, and Flask for small services where you want a minimal core and choose each extension yourself.

Is FastAPI faster than Django and Flask?

FastAPI on an ASGI server usually handles more concurrent I/O-bound requests than synchronous Flask or Django views, because handlers can await database and network calls. For most apps the database and external calls dominate response time, so the framework is rarely the bottleneck.

Can Django be used for APIs?

Yes. Most Django APIs use Django REST framework or django-ninja, which adds type-hint-driven schemas similar to FastAPI while keeping the Django ORM and admin.

Can I migrate from Flask to FastAPI?

Usually route by route. The routing decorators look similar, but request parsing moves from request.get_json() to Pydantic models in the signature, and synchronous extensions such as Flask-SQLAlchemy need async or sync replacements that work outside Flask.

Where does Encore fit with FastAPI and Django?

Encore is an infrastructure SDK you add to your existing app rather than a web framework, and it works with whichever framework you pick. You keep FastAPI, Django, Flask or any other framework for routing and request handling and declare Postgres databases, Pub/Sub topics, buckets, cron jobs and secrets in Python. encore run starts the databases, topics and buckets locally, cron jobs run only where the app is deployed, and a deploy provisions everything in your own AWS or GCP account. Python support is in beta.

How do I add a database and queues to my FastAPI app?

Install the encore-dev package in the app you already have and declare postgres.Database and pubsub.Topic as module-level variables next to your routes. encore run starts them locally and a deploy provisions them in your own AWS or GCP account, and the same SDK works with whichever framework you pick, so a Django app can use it too.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders