# Flask vs. FastAPI vs. Django technology-selection guide: which to choose in which situation (2026 edition, production-operation decision axes)

> A technology-selection guide comparing the differences of Flask, FastAPI, and Django from the viewpoint of production operation. It organizes architecture philosophy, sync/async, type validation, the admin screen, the learning curve, and current versions in a table, and shows the recommended framework by scenario — REST API, high-concurrency IO, instant admin screen, staged migration. The definitive guide to the Flask FastAPI Django difference, comparison, and selection.

- Published: 2026-06-19
- Author: 友田 陽大
- Tags: Python, Flask, FastAPI, Django, 技術選定, アーキテクチャ設計, バックエンド
- URL: https://tomodahinata.com/en/blog/flask-vs-fastapi-vs-django-comparison-guide
- Category: Flask in production
- Pillar guide: https://tomodahinata.com/en/blog/flask-production-guide

## Key points

- The three aren't competitors but a division of territory. Flask = a minimal core + the freedom to assemble yourself, FastAPI = type-driven validation, async, and auto-docs, Django = the all-in-one with ORM/admin/auth included.
- Sync or async is the first fork. High-concurrency IO-bound and WebSocket is FastAPI (ASGI), solid CRUD and fine control is Flask (WSGI), and Flask's async still occupies a worker so raw throughput doesn't rise.
- If you want the admin screen, auth, ORM, and migrations right now, Django. Conversely FastAPI/Flask have you assemble them yourself (thinner and choosable for that).
- FastAPI is still in the 0.x family as of now. It's stable, but needs the operational discipline of breaking changes and version pinning. Django's version system (LTS) is mature.
- Flask ↔ FastAPI can share marshmallow/Pydantic/SQLAlchemy, and coexistence and staged migration behind a gateway are realistic. There's also Quart as an escape route for Flask's async demand.

---

## **Introduction: "which is superior" is the wrong question**

"Flask, FastAPI, Django — in the end, which should I choose?" — there's **no single correct answer** to this question. The three are bundled into the same category of "Python web framework," but the problems they try to solve differ. It's not superiority/inferiority but a matter of fit: **"for your requirements, which reaches the goal with the least debt."**

This article is a **decision framework** to judge that fit. I designed and implemented the backend of a B2B SaaS that won the Minister of Economy, Trade and Industry Award with **Flask / SQLAlchemy / PostgreSQL** and operate it in production ([the lumber-distribution DX case study](/case-studies/lumber-industry-dx)), and on a separate project I've built an API service with **FastAPI** too. So this comparison is the viewpoint of "someone who has run both in production." That's exactly why I'll clearly write **the situations where I shouldn't recommend Flask** too. Read it not as a sales pitch but as a tool for technology selection.

> 💡 **The versions handled in this article (as of June 2026)**: **Flask 3.1 family** (latest stable 3.1.3, 2026-02 / min Python 3.9), **FastAPI 0.13x family** (around 0.138.x in mid-2026, still below 1.0 / requires Python 3.10+), **Django 6.0 family** (the current LTS is 5.2 LTS, extended support until April 2028 / the next LTS is 6.2 LTS planned for April 2027). Framework APIs and recommendations are based on the latest official documentation.

---

## **1. Conclusion first: a 30-second TL;DR and overall comparison table**

The one-line guideline when in doubt is this.

- **Want to build a REST/JSON API type-safely and high-concurrency → FastAPI**
- **Want to stand up the "whole web app" with admin, auth, ORM included in the shortest path → Django**
- **Want to choose the configuration one by one / build small-to-mid scale thinly → Flask**

Let me first make this intuition into a big picture with one table. Each section below digs into a row of this table.

| Aspect | Flask | FastAPI | Django |
|---|---|---|---|
| Philosophy | micro (core only, assemble yourself) | API-first (type-driven) | batteries-included (all-in) |
| Foundation | WSGI (Werkzeug + Jinja) | ASGI (Starlette + Pydantic) | own (WSGI/ASGI both) |
| Sync/async | sync-based (async bolted on) | async-native (`async def`) | sync-based (async partial support) |
| Type validation | none built in (add marshmallow / Pydantic) | type hints = validation with Pydantic | Forms / DRF Serializer |
| Admin screen | none (homemade or Flask-Admin) | none (homemade) | **auto-generated by default** |
| ORM | none (add SQLAlchemy, etc.) | none (add SQLAlchemy, etc.) | **Django ORM included** |
| Migration | none (Flask-Migrate/Alembic) | none (Alembic, etc.) | **makemigrations included** |
| Auth | none (Flask-Login, etc.) | none (homemade / library) | **auth/permissions included** |
| Auto API docs | none | **OpenAPI + Swagger UI auto** | separately with DRF |
| Learning curve | low (small core) / design needs honing | medium (need to understand types and async) | medium-high (many conventions) |
| Representative use | small-to-mid services, internal tools, projects needing selection freedom | high-concurrency API, microservices, type-contract emphasis | content/CRUD-centric whole web apps |
| Current version (2026/06) | 3.1.3 (stable) | 0.138.x (**0.x family**) | 6.0 / LTS is 5.2 |

> ⚠️ Don't read this table as a "score sheet." "No admin screen" is **not a defect of Flask/FastAPI but a design judgment.** Precisely because it doesn't have features you don't need, it's thin and fast. Conversely, if the admin screen is a requirement, Django, which "has" it, pulls a step ahead. **The key of comparison is not the abundance of features but the match with your requirements.**

---

## **2. Flask: a minimal core and the freedom of design (and its responsibility)**

### 2.1 Philosophy: you decide what to load

Flask is a **WSGI microframework.** Its foundation is **Werkzeug (the WSGI/HTTP layer) and Jinja (templates)**, and what Flask itself provides is **only the core** of "routing, request/response, templates, configuration, context." It **doesn't include** an ORM, form validation, an admin screen, or auth.

The official calling it "micro" doesn't mean "poor in features" but **"you decide what to load."** If you use a DB, Flask-SQLAlchemy; forms/CSRF, Flask-WTF; migrations, Flask-Migrate; API input/output validation, marshmallow — **explicitly incorporate only what you need as extensions.**

### 2.2 What it includes / doesn't include

| Category | Flask's handling | Typical addition |
|---|---|---|
| Routing, request | built in | — |
| Templates | built in (Jinja) | — |
| ORM | none | Flask-SQLAlchemy / SQLAlchemy |
| Migration | none | Flask-Migrate (Alembic wrapper) |
| Forms/CSRF | none | Flask-WTF |
| Input/output validation | none | marshmallow / Pydantic |
| Admin screen | none | Flask-Admin (or homemade) |
| Auth | none | Flask-Login / Authlib |

The minimal form is literally a few lines.

```python
# hello.py — Flask の「核」だけが見える最小形
from flask import Flask

app = Flask(__name__)


@app.get("/health")
def health():
    return {"status": "ok"}
```

### 2.3 The async story: it's bolted on, and throughput doesn't rise

Flask **supports `async def` views from 2.0** (`pip install flask[async]`). But misunderstand this and you'll get hurt. As the official clearly cautions, **each request still occupies 1 worker, and making a view `async` doesn't change "the number of requests handled concurrently."** Flask's async works for IO concurrency like "calling multiple external APIs in parallel within one view," **not throughput improvement.**

If full-fledged async (high concurrency, WebSocket, streaming) is a requirement, the official position is to consider **Quart**, which is ASGI-native with the same API as Flask. In other words, **the moment you choose Flask, it's the correct design judgment to resign that "raw high-concurrency async isn't the main battlefield."**

### 2.4 The type/validation story: protect the boundary yourself

Flask has no type-driven validation. You protect the API's entrance (request → internal) and exit (internal → response) yourself with **marshmallow** or **Pydantic.** What I've used in production is marshmallow, where `load()` handles input validation and `dump()` handles response formatting, and `dump_only` / `load_only` structurally prevent mass assignment and confidentiality leaks. I wrote this design in detail in [designing a production REST API with marshmallow × Flask × SQLAlchemy](/blog/marshmallow-flask-sqlalchemy-rest-api-production-guide).

### 2.5 Ecosystem maturity

Flask is long-lived and its extension ecosystem is vast. Its strength is that **general-purpose libraries not specific to Flask** like SQLAlchemy, Alembic, Celery, and Authlib can be used as-is. Conversely, since there's no "official correct set," **the good or bad of selection is left to the team.**

### 2.6 Suited situations / honest weaknesses

**Suited**: small-to-mid services, internal tools, projects where you want to scrutinize the configuration one by one, projects with existing SQLAlchemy assets, staged replacement of legacy.

**Honest weaknesses**:

- **You must design the structure yourself.** Without designing the application factory, Blueprints, configuration, context, and deployment, a small app slides straight into "legacy dominated by global variables and import order." This is not a feature deficiency of Flask but a matter of **design responsibility**; the big picture of production design is summarized in [the Flask production-operations guide (pillar)](/blog/flask-production-guide), and how to build a large-scale configuration in the [application-factory / Blueprint large-app structure guide](/blog/flask-application-factory-blueprints-large-app-structure-guide).
- **Async is bolted on**, and high concurrency isn't the main battlefield (§2.3).
- **No auto API docs.** If you want OpenAPI/Swagger, incorporate it separately.

> 💡 In my experience, most Flask failures come not from "Flask was weak" but from "**skipping structural design.**" Conversely, a Flask with `create_app` and layer separation (Router → Schema → Model) in from the start becomes a thinner, more readable, more testable backend than FastAPI or Django. Flask is a framework that demands "design responsibility" in exchange for "freedom."

---

## **3. FastAPI: type-driven validation and async-native**

### 3.1 Philosophy: get validation, conversion, and docs at once with standard type hints

FastAPI is a modern framework that **builds APIs on the foundation of standard Python type hints.** Its foundation is **Starlette (the ASGI web layer) and Pydantic (data validation)**, and the **`async def` path operation** is a first-class citizen. The biggest differentiator is that **just by writing type hints, validation, conversion, OpenAPI-schema generation, and interactive docs come automatically.**

```python
# main.py — 型ヒントが、そのまま検証とドキュメントになる
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class ItemIn(BaseModel):
    name: str
    price: float


@app.post("/items")
async def create_item(item: ItemIn) -> ItemIn:
    # item は検証済み。不正な JSON は FastAPI が 422 で弾く
    return item
```

With just this code, **interactive API docs are auto-generated** at `/docs` (Swagger UI) and `/redoc` (ReDoc). This is FastAPI-specific immediacy that Flask/Django don't have.

### 3.2 Async-native: this is the decisive difference from Flask

Because FastAPI stands on **ASGI**, it can **truly concurrently** handle IO-bound processing written with `async def`. High-concurrency network IO, WebSocket, and streaming responses are the main battlefield. But there's a different kind of pitfall here from Flask — **call synchronous blocking processing (a DB library that can't be `await`ed, etc.) inside `async def` and it stops the event loop and freezes the whole API.** Using `async def` and ordinary `def` separately requires discipline. This criterion is detailed in this site's [FastAPI production-operations guide](/blog/fastapi-production-async-pydantic-observability-guide) (where to use async is FastAPI's biggest leverage and its biggest pitfall).

### 3.3 The type/validation story: Pydantic at the core

FastAPI's validation is all **Pydantic.** Separate the input model and output model, and instantly model an external API's JSON with `model_validate` too — the design of "killing data at the boundary" can be written with type hints alone. Pydantic v2's core validation is written in Rust and fast. For the design of Pydantic itself, see the [Pydantic v2 production-validation / type-safety guide](/blog/pydantic-v2-production-validation-type-safety). Choosing FastAPI = placing Pydantic at the core.

### 3.4 What it includes / doesn't include

| Category | FastAPI's handling |
|---|---|
| Routing, async | built in (Starlette) |
| Input/output validation | **built in (Pydantic type hints)** |
| OpenAPI / Swagger UI / ReDoc | **auto-generated** |
| Dependency injection (DI) | **built in (`Depends`)** |
| ORM | none (add SQLAlchemy, etc.) |
| Migration | none (Alembic, etc.) |
| Admin screen | none |
| Auth | has primitives (OAuth2, etc., utilities). Implement yourself |

In other words, FastAPI is in an intermediate position of "**the parts needed for an API (validation, docs, DI) are batteries-included, but the whole web app (ORM, admin, auth) you assemble yourself.**"

### 3.5 Suited situations / honest weaknesses

**Suited**: high-concurrency IO-bound APIs, microservices, projects where you want to share a type-safe API contract across a team, projects where you want to align types with the frontend (TypeScript) via OpenAPI, AI/ML inference APIs.

**Honest weaknesses**:

- **Still in the 0.x family.** It's used stably and widely, but the version is below 1.0 and minor updates can include breaking changes. It needs the **operational discipline of version pinning (`==`) and CHANGELOG following.**
- **The batteries stop at the API.** ORM, migration, admin screen, and auth you still assemble yourself. Expect "all-in" like Django and you'll be let down.
- **The discipline of async correctness** is essential (§3.2).

---

## **4. Django: batteries-included and the power of conventions**

### 4.1 Philosophy: follow conventions and the "whole web app" stands up in the shortest path

Django is a **"batteries-included"** full-stack framework. It includes **ORM, an auto admin screen, auth/permissions, migrations, forms, and templates** from the start, and you assemble along the convention of the **MTV (Model-Template-View) architecture.** It supports both WSGI and ASGI.

Its biggest weapon is **`django-admin` (the auto admin screen).** Just define a model and register a few lines, and a management UI with CRUD, search, and permissions is auto-generated. This is Django-specific immediacy that doesn't exist in Flask/FastAPI.

```python
# models.py — モデルを定義し…
from django.db import models


class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    published_at = models.DateTimeField(null=True, blank=True)
```

```python
# admin.py — 数行登録するだけで、CRUD 管理画面が自動で生える
from django.contrib import admin
from .models import Article

admin.site.register(Article)
```

### 4.2 What it includes / doesn't include

| Category | Django's handling |
|---|---|
| ORM | **built in (Django ORM)** |
| Migration | **built in (makemigrations / migrate)** |
| Admin screen | **built in (auto-generated)** |
| Auth/permissions | **built in** |
| Forms | **built in** |
| Templates | **built in** |
| REST API | adding DRF (Django REST Framework) is the standard |
| async | **partial support** (some of views and ORM) |

### 4.3 The API story: DRF is standard

To build a JSON API with Django, loading **Django REST Framework (DRF)** is the standard. DRF's Serializer handles input/output validation/formatting. But there's no unity of "type hints = validation = auto OpenAPI" like FastAPI, and there's the writing volume of writing a separate Serializer. **"FastAPI if API-only, Django + DRF if the API is also needed as part of an admin-screen-equipped web app"** is the rough fork.

### 4.4 The async story: partial support

Django supports async views and some async ORM, but **it's not async-native like FastAPI.** Because the whole ecosystem still includes many sync-premised spots, if "high-concurrency async is the main purpose," FastAPI is more straightforward.

### 4.5 Suited situations / honest weaknesses

**Suited**: content-management / CRUD-centric whole web apps, projects where the admin screen and auth are instantly mandatory, projects where you want to build fast as a team riding the conventions, internal business systems.

**Honest weaknesses**:

- **Heavy and opinionated.** For requirements that don't ride Django's conventions, it instead becomes a fight. It's overkill for projects that "want only the core."
- **Verbose for API-only.** For a pure JSON API, DRF's writing volume and the ORM's constraints feel heavy against FastAPI's lightness.
- **Async is partial** (§4.4).

---

## **5. The decision framework: scenario → recommendation**

Let me land the details so far into **actual requirements.** Your starting point is decided by which row your project is closest to.

| Your scenario | First candidate | Reason | Runner-up |
|---|---|---|---|
| REST/JSON API-centric. Want to share the type contract with the front | **FastAPI** | type hints = validation = auto OpenAPI | Flask + marshmallow |
| High-concurrency IO-bound, WebSocket, streaming | **FastAPI** | true async concurrency with ASGI | Quart |
| Admin screen, auth, ORM right now and mandatory | **Django** | all included, admin auto-generated | — |
| Content/CRUD-centric whole web app | **Django** | shortest arrival with MTV conventions | Flask |
| Want to choose the configuration one by one, build thinly | **Flask** | core only, assemble with extensions | FastAPI |
| Want to start small and expand to fit requirements | **Flask** | grow incrementally from minimal | FastAPI |
| Have existing SQLAlchemy / Flask assets, team skills | **Flask** | minimal learning and migration cost | FastAPI |
| AI/ML inference API (type-safe I/O) | **FastAPI** | strict I/O with Pydantic | Flask |
| Internal business system (permission management is key) | **Django** | auth/permissions/admin included | Flask |
| Many lightweight microservices lined up | **FastAPI** | lightweight, async, auto-docs | Flask |

> 💡 It can also be organized with **three decision questions**. ① "Do you want the admin screen, auth, and ORM all right now?" → Yes → **Django.** ② (If No) "Is high-concurrency async / a type-driven API the main purpose?" → Yes → **FastAPI.** ③ (If No to both) "Do you want to choose the configuration yourself and build thinly?" → Yes → **Flask.** Ask in this order and most projects naturally converge to one.

---

## **6. When to choose Flask / when not to (the honest version)**

Precisely because I've operated Flask in production myself, I'll write this clearly.

### 6.1 When to choose Flask

- **Want to scrutinize and choose the configuration**: want to choose the ORM, validation, auth, and task queue one by one, optimal for the project.
- **Want to build small-to-mid scale thinly**: want to not pile on features and fit the requirements minimally.
- **Have existing assets**: have SQLAlchemy / marshmallow / an existing Flask codebase, and want to keep learning/migration cost down.
- **Sync processing is the main**: typical CRUD / business logic, where ultra-high-concurrency async isn't a requirement.
- **Have the resolve to take on design**: can put in `create_app`, Blueprints, and layer separation from the start ([large-app structure guide](/blog/flask-application-factory-blueprints-large-app-structure-guide)).

### 6.2 When not to choose Flask

- **High-concurrency async is the main purpose**: if WebSocket, massive simultaneous connections, or streaming is central, **FastAPI** (or Quart).
- **Want the admin screen, auth, and ORM all instantly**: if you don't want to pay the homemade effort, **Django.**
- **Want auto API docs as a premise**: if you want to "get OpenAPI/Swagger without writing it," **FastAPI.**
- **Want to make the type-driven API contract a team discipline**: if you place Pydantic at the core, **FastAPI.**
- **No structure to take on design**: if it'll become an operation that skips structural design, **Django**, which protects you with conventions, has fewer accidents.

> ⚠️ The last point is important. Flask's "freedom," unless paired with design discipline, becomes **debt.** If the team has no mechanism to guarantee design, the convention-strong Django is sometimes safer in the end. "Freedom = good" isn't true.

---

## **7. Migration and coexistence: the three are continuous ground**

Technology selection isn't a "one-time bet." The three are **more continuous than you think**, and there are realistic paths for migration and coexistence.

### 7.1 Parts can be shared

- **Flask ↔ FastAPI**: **marshmallow / Pydantic / SQLAlchemy** are all framework-independent. You can swap only the web layer while sharing the persistence layer (SQLAlchemy models) and validation logic. For SQLAlchemy 2.0 design, see the [SQLAlchemy 2.0 typed ORM practical guide](/blog/sqlalchemy-2-typed-orm-production-guide) (the same ORM is usable in both Flask and FastAPI).

### 7.2 Coexist behind a gateway

Behind an API Gateway or ALB, you can **coexist a Flask app and a FastAPI app by routing on path.** A **staged migration** of writing only new endpoints in FastAPI and leaving the existing as Flask is realistic. The lumber-distribution SaaS I've operated also had a configuration of bundling services with API Gateway → ALB → ECS. You can migrate per endpoint, without concentrating the risk on a bulk migration.

### 7.3 Flask's async escape route: Quart

When a team used to Flask's syntax needs async, migrating to **Quart** (an ASGI framework with a Flask-compatible API) is sometimes a smaller step than learning FastAPI from scratch. Keep it in a corner of your mind as the option of "leaning to async while staying Flask."

```text
[ client ]
       │
   [ API Gateway / ALB ]
     ├── /api/legacy/*  → Flask app (existing, sync)
     └── /api/v2/*      → FastAPI app (new, async)
```

> 💡 No need to be intense about "I must make a perfect choice at the start." **Keep the parts (ORM, validation) in a shareable form and the web layer loosely coupled, and you can migrate later.** Technology selection can leave reversibility — this is the wisdom of ordering that also connects to the viewpoint of vendor selection and procurement ([system-development outsourcing guide: vendor selection and cost](/blog/system-development-outsourcing-guide-vendor-selection-cost)).

---

## **8. For those who want to know FastAPI deeply**

This article is a selection guide for "which to choose." The production operation **after deciding to choose FastAPI** — the use of `async def` / `def`, Pydantic v2 boundary validation, dependency injection with `Depends`, observability with structured logs and OpenTelemetry, and the limits of `BackgroundTasks` and how to escape to a job foundation — is systematized in a separate article. For FastAPI-specific design judgments, see the [FastAPI production-operations guide](/blog/fastapi-production-async-pydantic-observability-guide). Similarly, the production design after choosing Flask starts from the [Flask production-operations guide](/blog/flask-production-guide).

---

## **Conclusion: the recommendation matrix**

Finally, let me condense the judgment into one sheet.

| If… | Choose | A word |
|---|---|---|
| High-concurrency, type-safe API is the main purpose | **FastAPI** | ASGI and Pydantic are the main battlefield. But keep the 0.x version discipline |
| Admin screen, auth, ORM instantly mandatory | **Django** | all-in. Shortest if you ride the conventions. API is DRF |
| Content/CRUD whole web app | **Django** | stands up fast with MTV conventions and admin |
| Want to choose the configuration yourself, build thinly | **Flask** | core only. Design responsibility in exchange for freedom |
| Start small and expand incrementally | **Flask** | grow from minimal. Migration possible later |
| Have existing SQLAlchemy/Flask assets | **Flask** | minimal learning/migration cost |
| No structure to guarantee design | **Django** | conventions reduce accidents |

The three aren't competitors but a **division of territory.** Flask is "maximum control and minimal core," FastAPI is "type-driven validation, async, and auto-docs," Django is "shortest arrival with conventions and comprehensiveness." Run your requirements through §5's three questions and the starting point is naturally decided. And as in §7, keep the parts loosely coupled and **you can migrate later** — so don't fall into perfectionism, and choose the one that minimally fits your current requirements. That's the shortcut to a backend that holds value for a long time.
