結論

Python で Web アプリを作るなら、画面中心の小さなものは Flask、JSON を返す API が中心なら FastAPI、ログイン・管理画面・DB まで最初から揃えたいなら Django です。3つとも「venv を作る→pip で入れる→1ファイル書く→起動→ブラウザで開く」の流れは同じで、違いは「どこまで最初から入っているか」です。

この記事でわかること

  • Flask・FastAPI・Django の違いと、最初の1本をどれで作るかの判断表
  • 同じ「フォームで登録→一覧」アプリを3つで作った全コード・起動ログ・画面
  • コード量・依存パッケージ数・速さ(req/s)・エラー原文の実測

2026年10月29日更新|Flask 3.1.3・FastAPI 0.162.0・Django 6.1.2・Python 3.13.18・Chromium 153 で実行確認

Web アプリは「ブラウザからの HTTP リクエストを受け取り、処理して、HTML か JSON を返すプログラム」です。Python では、この受け取りと返しをフレームワークが担当し、作る人は「どの URL で何をするか」だけを書きます。3つのフレームワークは、その「何をするか」の周りにどこまで道具を用意しているかが違います。

flowchart TD
    S[最初の1本] --> Q1{JSON の API<br>が中心?}
    Q1 -->|はい| F[FastAPI]
    Q1 -->|いいえ| Q2{ログイン・<br>管理画面が要る?}
    Q2 -->|はい| D[Django]
    Q2 -->|いいえ| K[Flask]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
Python で Web アプリを作る手順 venv→pip install→1ファイル→起動→127.0.0.1 で開く 準備:venv と pip install
Flask・FastAPI・Django の違い 同梱の範囲が違う。小さい順に Flask<FastAPI<Django Flask・FastAPI・Django の違い
どれを選ぶか 画面の小物は Flask、API は FastAPI、会員制は Django どれを選ぶか(判断表)
一番速いのは 手元の実測では FastAPI(uvicorn)。差は 1.3〜3倍 速さの比較(実測)
一番コードが短いのは Flask と FastAPI が 2ファイル約60行。Django は5ファイル54行+自動生成11ファイル 作ったもの
データベース(SQLite)は Flask・FastAPI は標準の sqlite3、Django は ORM と migrate SQLite との接続
公開(デプロイ)は 開発サーバーは使わない。gunicorn・uvicorn か PaaS デプロイの選択肢
起動できない・画面が出ない venv・ポート・テンプレートの場所・INSTALLED_APPS よくあるエラー

確認環境:Ubuntu 24.04.5 LTS(2 CPU)、Python 3.13.18 の python3 -m venv に pip 26.2.2 で Flask 3.1.3(Werkzeug 3.1.9・Jinja2 3.1.6)、FastAPI 0.162.0(Starlette 1.10.0・pydantic 2.14.1・uvicorn 0.54.0・python-multipart 0.0.32)、Django 6.1.2、gunicorn 26.2.0 を入れました。同じ機能のアプリを3つ書き、127.0.0.1 の別々のポートで起動し、curl 8.5.0 と、Playwright 1.63.0 が動かす Chromium 153.0.8010.12 でフォームの送信と画面を確かめました。速さは自作の計測スクリプト(標準ライブラリの http.client を20スレッドで並列実行)で測りました。デプロイの選択肢・対応する Python の版は、Flask・Django・FastAPI の公式ドキュメントで確かめました(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PythonでWebアプリを作る|Flask・FastAPI・Django実測比較」

Flask・FastAPI・Djangoの違い(一覧表)

この節では、3つのフレームワークが何を同梱していて、何を自分で足すのかが一覧で分かります。

3つの違いは「速いか遅いか」より「最初から何が入っているか」です。 Flask は最小、FastAPI は API に要るもの(型による入力検査・API ドキュメント)、Django は Web サイトに要るもの(ORM・管理画面・認証・フォーム)を同梱しています。

Flask 3.1 FastAPI 0.162 Django 6.1
性格 最小構成。必要なものを自分で足す API 向け。型ヒントで入力を検査 全部入り。決まった構成に沿って作る
pip install で入る数(自分を含む) 7 パッケージ(3MB) 13(standard 付きで 59。65MB) 3 パッケージ(32MB)
対応する Python 3.9 以上 3.10 以上 3.12〜3.14
テンプレート Jinja2(同梱) Jinja2(別途 pip) Django テンプレート(同梱)
DB の扱い 自分で選ぶ(sqlite3・SQLAlchemy など) 自分で選ぶ(sqlite3・SQLAlchemy など) ORM と migrate が同梱
管理画面・ログイン 拡張を足す 拡張を足す admin と auth が同梱
API ドキュメント 拡張を足す /docs が自動で付く DRF などを足す
非同期(async) 対応(WSGI が基本) 前提(ASGI) 対応(WSGI・ASGI 両方)
開発サーバーの起動 flask run uvicorn main:app/fastapi dev manage.py runserver

パッケージ数と大きさは、空の venv に pip install flask・pip install fastapi・pip install django をそれぞれ行い、pip list と site-packages の大きさ(pip を除く)を測ったものです。FastAPI は pip install "fastapi[standard]" にすると uvicorn・Jinja2・python-multipart・fastapi コマンドが一緒に入り、59 パッケージになりました。

作ったもの:同じ「フォームで登録→一覧」を3つで

この節では、比較のために作ったアプリの機能と、3つのコード量・ファイル数が分かります。

「名前とメモをフォームで送ると SQLite に保存され、一覧に出る」アプリを、3つのフレームワークで同じ仕様で作りました。 空の名前は 400 で弾き、/api/items で JSON も返します。

3つに共通の仕様

GET / … フォームと一覧(HTML)

POST … 名前・メモを保存して / に戻る

名前が空なら 400 と「名前は必須です」

GET /api/items … 一覧を JSON で返す

置き場所と起動ポート

Flask:flaskapp/ → 127.0.0.1:46101

FastAPI:fastapiapp/ → 127.0.0.1:46102

Django:mysite/ → 127.0.0.1:46103

DB はいずれも SQLite のファイル1つ

Flask FastAPI Django
自分で書いたファイル 2(app.py・index.html) 2(main.py・index.html) 5(models・forms・views・urls・index.html)+settings に1行
自分で書いた行数 61 行 63 行 54 行
コマンドが作ったファイル 0 0 11(manage.py・settings.py・migrations など)
SQL を書いたか 書いた(CREATE・INSERT・SELECT) 書いた 書いていない(ORM が生成)
CSRF 対策 自分で足す 自分で足す 既定で有効(トークン無しは 403)

自分で書く行数は3つともほぼ同じでした。 違いは、Flask・FastAPI では SQL と DB の接続を自分で書き、Django ではその代わりに startproject・startapp・migrate というコマンドと決まった置き場所を覚える、という点です。

準備:venvとpip install(Python Webアプリの作り方の最初の一歩)

この節では、3つに共通する準備の手順と、入れたときの出力が分かります。

どのフレームワークでも、最初に仮想環境(venv)を作り、その中に pip で入れます。 OS の Python に直接入れると、別のプロジェクトと版がぶつかります。

1

仮想環境を作る

python3 -m venv venv。Windows は py -m venv venv。有効化は Linux・macOS が source venv/bin/activate、Windows が venv\Scripts\activate。

Python 3.13.18 で 2.8 秒

2

フレームワークを入れる

pip install flask/pip install "fastapi[standard]"/pip install django。3つ試すなら1つの venv に全部入れても動く(この記事はそうした)。

pip 26.2.2

3

1ファイル書いて起動する

次の節のコードを置き、flask run・uvicorn main:app・python manage.py runserver のどれかで起動。

既定のポートは 5000・8000・8000

4

ブラウザで 127.0.0.1 を開く

http://127.0.0.1:ポート/。起動ログに出る URL をそのまま開く。

Ctrl+C で止める

venv の作り方と有効化の OS 別の違いは、関連記事「Python venvの使い方|仮想環境の作成・有効化・削除をOS別にコピペ」で、pip より速い uv での作り方は、関連記事「uv venvとuvの使い方|仮想環境の作成・有効化とpipとの速さ比較」で扱っています。

入れたあとの pip list です(Flask だけを入れた venv)。

$ pip list
Package      Version
------------ -------
blinker      1.9.0
click        8.5.0
Flask        3.1.3
itsdangerous 2.2.0
Jinja2       3.1.6
MarkupSafe   3.0.4
pip          26.2.2
Werkzeug     3.1.9

Django は Django 6.1.2・asgiref 3.13.0・sqlparse 0.6.0 の3つだけで、Flask より少ないパッケージ数で 10 倍の大きさ(32MB)でした。全部が1つのパッケージに入っているためです。

Flaskで作る(app.pyとテンプレート)

この節では、Flask で作ったアプリの全コードと、起動したときの出力が分かります。

Flask は @app.get("/") のように「URL と関数」を結びつけるだけで動きます。 DB の接続は Python 標準の sqlite3 を使い、リクエストごとに開いて閉じます。

# flaskapp/app.py
import sqlite3
from flask import Flask, render_template, request, redirect, url_for, jsonify, g

app = Flask(__name__)
DB = "items.db"

def get_db():
    if "db" not in g:                       # g はリクエストの間だけ生きる置き場
        g.db = sqlite3.connect(DB)
        g.db.row_factory = sqlite3.Row
    return g.db

@app.teardown_appcontext
def close_db(exc):
    db = g.pop("db", None)
    if db is not None:
        db.close()

def init_db():
    with sqlite3.connect(DB) as con:
        con.execute("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT NOT NULL, memo TEXT)")

@app.get("/")
def index():
    rows = get_db().execute("SELECT * FROM items ORDER BY id DESC").fetchall()
    return render_template("index.html", items=rows, error=None)

@app.post("/add")
def add():
    name = request.form.get("name", "").strip()
    memo = request.form.get("memo", "")
    if not name:
        rows = get_db().execute("SELECT * FROM items ORDER BY id DESC").fetchall()
        return render_template("index.html", items=rows, error="名前は必須です"), 400
    db = get_db()
    db.execute("INSERT INTO items (name, memo) VALUES (?, ?)", (name, memo))
    db.commit()
    return redirect(url_for("index"))

@app.get("/api/items")
def api_items():
    rows = get_db().execute("SELECT * FROM items ORDER BY id DESC").fetchall()
    return jsonify([dict(r) for r in rows])

init_db()

テンプレートは templates/index.html に置きます(Flask はこのフォルダー名を既定で探します)。FastAPI でも同じファイルをそのまま使いました。

<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><title>登録フォーム(Flask)</title></head>
<body>
<h1>登録フォーム(Flask)</h1>
<form method="post" action="/add">
  <label>名前 <input name="name"></label>
  <label>メモ <input name="memo"></label>
  <button type="submit">登録</button>
</form>
{% if error %}<p class="error">{{ error }}</p>{% endif %}
<ul>
{% for it in items %}
  <li>{{ it.id }}: {{ it.name }} — {{ it.memo }}</li>
{% endfor %}
</ul>
</body></html>

起動と、curl で送った結果です。

$ flask --app app run --host 127.0.0.1 --port 46101
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:46101
Press CTRL+C to quit

$ curl -i -X POST --data-urlencode 'name=たかひら' --data-urlencode 'memo=最初の登録' http://127.0.0.1:46101/add
HTTP/1.1 302 FOUND
Server: Werkzeug/3.1.9 Python/3.13.18
Location: /

$ curl -i -X POST -d 'name=&memo=x' http://127.0.0.1:46101/add
HTTP/1.1 400 BAD REQUEST

$ curl http://127.0.0.1:46101/api/items
[{"id":1,"memo":"最初の登録","name":"たかひら"}]

Flask の jsonify は日本語を た のような形に変換して返します(JSON として正しく、ブラウザや他の言語では日本語に戻ります)。そのまま日本語で返したいときは app.json.ensure_ascii = False を設定します。

FastAPIで作る(型ヒントとForm・/docs)

この節では、FastAPI で同じアプリを作ったときのコードと、Flask との書き方の違いが分かります。

FastAPI は、関数の引数に書いた型(name: str = Form("") や response_model=list[Item])をそのまま入力の検査と API ドキュメントに使います。 HTML を返すときは Jinja2 を別に入れ、Jinja2Templates で使います。

# fastapiapp/main.py
import sqlite3
from contextlib import asynccontextmanager
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

@asynccontextmanager
async def lifespan(app):                      # 起動時に1回だけ実行
    with db() as con:
        con.execute("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT NOT NULL, memo TEXT)")
    yield

app = FastAPI(lifespan=lifespan)
templates = Jinja2Templates(directory="templates")
DB = "items.db"

class Item(BaseModel):                        # 返す JSON の形
    id: int
    name: str
    memo: str | None = None

def db():
    con = sqlite3.connect(DB)
    con.row_factory = sqlite3.Row
    return con

def all_items():
    with db() as con:
        return [dict(r) for r in con.execute("SELECT * FROM items ORDER BY id DESC")]

@app.get("/", response_class=HTMLResponse)
def index(request: Request):
    return templates.TemplateResponse(request, "index.html", {"items": all_items(), "error": None})

@app.post("/add")
def add(request: Request, name: str = Form(""), memo: str = Form("")):
    name = name.strip()
    if not name:
        return templates.TemplateResponse(request, "index.html",
                                          {"items": all_items(), "error": "名前は必須です"}, status_code=400)
    with db() as con:
        con.execute("INSERT INTO items (name, memo) VALUES (?, ?)", (name, memo))
    return RedirectResponse("/", status_code=303)

@app.get("/api/items", response_model=list[Item])
def api_items():
    return all_items()
$ uvicorn main:app --host 127.0.0.1 --port 46102
INFO:     Started server process [1093]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:46102 (Press CTRL+C to quit)
INFO:     127.0.0.1:39582 - "POST /add HTTP/1.1" 303 See Other
INFO:     127.0.0.1:39590 - "POST /add HTTP/1.1" 400 Bad Request
INFO:     127.0.0.1:39614 - "GET /docs HTTP/1.1" 200 OK

$ curl http://127.0.0.1:46102/api/items
[{"id":1,"name":"たかひら","memo":"最初の登録"}]

1/docs が自動で付く

何も書かずに /docs(Swagger UI)と /openapi.json が返った。openapi.json には3つのパスと、Item の形(id・name が必須、memo は null 可)が入っていた。Flask の /docs は 404。

2POST のあとは 303

フォーム送信後の移動は RedirectResponse("/", status_code=303) と書く。既定の 307 だと、ブラウザが POST のまま / に送り直して 405 になる。

3日本語がそのまま返る

FastAPI の JSON は ensure_ascii=False 相当で、Flask・Django のような \uXXXX にならない。どちらも JSON としては正しい。

FastAPI で JSON の CRUD API を一式作る手順・422 の読み方・pytest は、関連記事「FastAPI入門|uvで作るCRUD API完成コードと実行結果」で扱っています。この記事は3つの比較と最初の1本の選び方に絞っています。

Djangoで作る(startproject→モデル→migrate→ビュー)

この節では、Django で同じアプリを作るときの手順と、コマンドが作るもの・自分で書くものが分かります。

Django は、まずコマンドでプロジェクトとアプリの骨組みを作り、決まった場所にモデル・ビュー・URL・テンプレートを書きます。 SQL は書かず、モデルから makemigrations・migrate でテーブルを作ります。

$ django-admin startproject mysite
$ cd mysite
$ python manage.py startapp items
$ python manage.py makemigrations items
Migrations for 'items':
  items/migrations/0001_initial.py
    + Create model Item
$ python manage.py migrate
  ...
  Applying items.0001_initial... OK
  Applying sessions.0001_initial... OK

自分で書いた5ファイルです。settings.py の INSTALLED_APPS に "items", を1行足しています。

# items/models.py
from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=100)
    memo = models.CharField(max_length=200, blank=True)

# items/forms.py
from django import forms
from .models import Item

class ItemForm(forms.ModelForm):
    class Meta:
        model = Item
        fields = ["name", "memo"]

# items/views.py
from django.http import JsonResponse
from django.shortcuts import render, redirect
from .forms import ItemForm
from .models import Item

def index(request):
    form = ItemForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        form.save()                           # INSERT は ORM が行う
        return redirect("index")
    items = Item.objects.order_by("-id")
    status = 400 if request.method == "POST" else 200
    return render(request, "items/index.html", {"form": form, "items": items}, status=status)

def api_items(request):
    return JsonResponse(list(Item.objects.order_by("-id").values()), safe=False)

# mysite/urls.py
from django.contrib import admin
from django.urls import path
from items import views

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.index, name="index"),
    path("api/items", views.api_items),
]

テンプレートは items/templates/items/index.html に置きます。Flask 版との違いは、フォームの中に {% csrf_token %} を書くことと、エラーの出し方だけです。

<form method="post" action="/">
  {% csrf_token %}
  <label>名前 <input name="name"></label>
  <label>メモ <input name="memo"></label>
  <button type="submit">登録</button>
</form>
{% if form.errors %}<p class="error">{{ form.name.errors.0 }}</p>{% endif %}
$ python manage.py runserver 127.0.0.1:46103
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
October 29, 2026 - 09:44:25
Django version 6.1.2, using settings 'mysite.settings'
Starting WSGI development server at http://127.0.0.1:46103/
Quit the server with CONTROL-C.

WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.

$ curl -i -X POST -d 'name=たかひら' http://127.0.0.1:46103/        # CSRF トークン無し
HTTP/1.1 403 Forbidden
CSRF verification failed. Request aborted.

$ curl -i http://127.0.0.1:46103/admin/
HTTP/1.1 302 Found          # ログイン画面へ。管理画面は最初から付いている

Chromium で /admin/ を開くと「Django administration」のログイン画面が出ました。createsuperuser でユーザーを作り、admin.py に admin.site.register(Item) と1行書くと、登録したデータの一覧・編集・削除の画面が付きます。

起動ログと画面の比較(Chromiumで確認)

この節では、3つのアプリをブラウザで操作したときに、同じ画面になるか・違いがどこに出るかが分かります。

Chromium でフォームに入力して送信すると、3つとも一覧に追加され、空で送るとエラーの文が出ました。 違いが出たのはエラーの文言です。

Flask FastAPI Django
送信後の URL・タイトル /・登録フォーム(Flask) /・登録フォーム(FastAPI) /・登録フォーム(Django)
一覧の1行目 3: ブラウザから登録 — flask のフォーム 3: ブラウザから登録 — fastapi のフォーム 2: ブラウザから登録 — django のフォーム
空で送ったときの表示 名前は必須です(400) 名前は必須です(400) This field is required.(400)
送信後の移動 302 FOUND 303 See Other 302 Found
応答の Server ヘッダー Werkzeug/3.1.9 Python/3.13.18 uvicorn WSGIServer/0.2 CPython/3.13.18
  • Django の「This field is required.」は、ModelForm が自動で出した文言です。 自分でエラーの文を書いていません。日本語にするには settings.py の LANGUAGE_CODE = "ja" にします(Django の翻訳が同梱されています)。
  • Django の応答には X-Frame-Options: DENY・X-Content-Type-Options: nosniff が最初から付いていました。Flask・FastAPI にはありません。
  • Playwright からの操作は、page.fill("input[name=name]", "…") → page.click("button[type=submit]") → page.locator("li").first.inner_text() の3行で、3つとも同じコードで動きました。

速さの比較(実測:req/sと応答時間)

この節では、同じ処理(SQLite から3件読んで JSON を返す)を3つのフレームワークで回したときの速さが分かります。

開発サーバーのままでは FastAPI(uvicorn)が最も速く、Django の runserver が最も遅い結果でした。本番用サーバーに替えると3つとも 1.4〜3.4 倍になり、差は縮まります。 20 並列で 1,000 リクエストを3回流し、中央の回の値です。

758req/sFlask(flask run)中央値 26.0ms・p95 34.3ms
1,028req/sFastAPI(uvicorn 1プロセス)中央値 18.9ms・p95 27.7ms
306req/sDjango(runserver)中央値 26.0ms・p95 43.9ms
起動のしかた Flask FastAPI Django
開発サーバー(1プロセス) 758 req/s 1,028 req/s 306 req/s
本番用(gunicorn/uvicorn、2ワーカー) 1,391 req/s 1,845 req/s 1,043 req/s
本番用の応答時間(中央値/p95) 13.6/19.8ms 9.8/17.0ms 18.1/25.7ms
本番用の起動コマンド gunicorn -w 2 app:app uvicorn main:app --workers 2 gunicorn -w 2 mysite.wsgi

計測に使ったスクリプトの中心です(標準ライブラリだけ。ab が無い環境でも動きます)。

def one(_):
    t = time.perf_counter()
    con = http.client.HTTPConnection("127.0.0.1", port, timeout=10)
    con.request("GET", path); r = con.getresponse(); r.read(); con.close()
    return time.perf_counter() - t, r.status

with ThreadPoolExecutor(max_workers=20) as ex:
    t0 = time.perf_counter(); res = list(ex.map(one, range(1000))); total = time.perf_counter() - t0
  • この差は「フレームワークの速さ」より「サーバーの作り」の差です。 Django の runserver は単純なスレッド型の開発用サーバーで、gunicorn に替えただけで 3.4 倍になりました。
  • 2 CPU・SQLite・3件のデータという小さな条件での値です。DB が重くなるほど、フレームワークの差はさらに小さくなります。
  • FastAPI の関数は def(同期)で書いています。async def にすると sqlite3 の呼び出しがイベントループを止めるため、同期 I/O を使うときは def のままにします(FastAPI はスレッドプールで動かします)。

SQLiteとの接続:素のsqlite3とORMの違い

この節では、同じ「保存して読む」を、Flask・FastAPI(sqlite3)と Django(ORM)でどう書くかの違いが分かります。

Flask・FastAPI は Python 標準の sqlite3 で SQL を直接書き、Django はモデルのクラスを書いて ORM に SQL を作らせます。 手元ではどちらも同じ items テーブルになりました。

したいこと sqlite3(Flask・FastAPI) Django ORM
テーブルを作る CREATE TABLE IF NOT EXISTS … を起動時に実行 models.py を書き makemigrations→migrate
1件入れる execute("INSERT … VALUES (?, ?)", (name, memo))+commit() form.save() か Item.objects.create(name=…)
一覧を読む execute("SELECT * FROM items ORDER BY id DESC") Item.objects.order_by("-id")
列を増やす ALTER TABLE を自分で書く モデルに足して makemigrations→migrate
DB を PostgreSQL に替える ライブラリと SQL の方言を自分で直す DATABASES の設定を変える

Flask・FastAPI で DB の表を増やしていくなら、SQL を直接書く代わりに SQLAlchemy(ORM)を足す選択肢があります。SQLite 自体のコマンドと Python からの使い方は、関連記事「SQLiteの使い方|コマンド早見表とPython連携を実行例で解説」で扱っています。

SQL に値を入れるとき

NGf"INSERT INTO items (name) VALUES ('{name}')" のように文字列で組み立てる。入力に ' が入ると壊れ、SQL インジェクションの入口になる
→
OKexecute("INSERT INTO items (name) VALUES (?)", (name,)) とプレースホルダーで渡す。Django の ORM はこれを自動で行う

手元の3つのアプリはすべてプレースホルダーか ORM です

テンプレート・フォーム・CSRFの違い

この節では、HTML を返す仕組みと、フォームの入力検査・CSRF 対策が3つでどう違うかが分かります。

テンプレートの書き方は Jinja2(Flask・FastAPI)と Django テンプレートでほぼ同じですが、フォームの検査と CSRF 対策は Django だけが同梱です。

Flask FastAPI Django
テンプレート Jinja2。render_template("index.html", items=…) Jinja2。templates.TemplateResponse(request, "index.html", {…}) Django テンプレート。render(request, "items/index.html", {…})
探す場所 templates/(app.py と同じ階層) directory="templates"(起動した場所から) 各アプリの templates/(INSTALLED_APPS に要登録)
フォームの値 request.form["name"] 引数 name: str = Form() ItemForm(request.POST)→is_valid()
空のとき 自分で判定して 400 Form()(既定値なし)なら自動で 422 ModelForm が「This field is required.」
CSRF Flask-WTF などを足す 自分で実装するか拡張 既定で有効。{% csrf_token %} 無しは 403
HTML エスケープ 自動(Jinja2) 自動(Jinja2) 自動

FastAPI で name: str = Form() と既定値を付けずに書くと、空で送ったときにフレームワークが 422 と {"detail":[{"type":"missing", …}]} を返します。手元のアプリでは HTML の画面に「名前は必須です」を出したかったので、既定値 Form("") を付けて自分で 400 を返しました。

どれを選ぶか(判断表とフロー)

この節では、最初の1本をどのフレームワークで作るかを、作りたいものから決める基準が分かります。

「最初の1本」は、作りたいものの形で決めます。学びやすさの差より、同梱されているものが合っているかの差のほうが大きいからです。

  1. 1返すのが JSON だけ(画面は別のフロントや他のアプリ)→ FastAPI型で入力を検査し、/docs で試せる。機械学習モデルの API もここ
  2. 2ログイン・会員・管理画面・複数テーブルが要る → Djangoauth・admin・ORM・migrate を自分で組まずに済む
  3. 3画面が数枚で DB は1〜2表、とにかく早く動かしたい → Flask1ファイルから始め、必要になったら拡張を足す
  4. 4Web の仕組み(HTTP・ルーティング・テンプレート)を学ぶのが目的 → Flask隠れている部分が少なく、何が起きているか追いやすい
  5. 5チームや既存のプロジェクトがどれかを使っている → それに合わせる3つとも同じ機能は作れる。揃えるほうが保守しやすい
作りたいもの 向いている 理由
社内向けの簡単な入力フォームと一覧 Flask この記事の Flask 版そのまま。2ファイルで済む
スマホアプリや React から呼ぶ API FastAPI JSON・型検査・/docs・非同期が揃っている
機械学習モデルを HTTP で呼べるようにする FastAPI pydantic で入力の形を固定でき、非同期で待ち時間を重ねられる
会員制サイト・ブログ・EC・予約 Django 認証・管理画面・ORM・フォーム・CSRF が同梱
複数のテーブルを管理者が編集する業務画面 Django admin に1行登録するだけで編集画面が付く
Web の仕組みの学習・個人の小さな道具 Flask 隠れている部分が少ない
Django のサイトに JSON の API を足す Django(DRF か Django Ninja) 別のフレームワークを増やすより、同じ ORM を使う
F

Flask を選んだら次に

Blueprint で画面を分ける。DB が増えたら SQLAlchemy、フォームが増えたら Flask-WTF、ログインは Flask-Login を足す。

足し算で育てる

A

FastAPI を選んだら次に

pydantic のモデルで入出力を固定し、Depends で DB 接続と認証を共通化する。画面が要るなら Jinja2 か別のフロント。

型を先に決める

D

Django を選んだら次に

公式チュートリアル(投票アプリ)を一度通す。createsuperuser で admin に入り、モデルを足すたびに migrate する癖を付ける。

決まりに従う

デプロイ(公開)の選択肢

この節では、作ったアプリを自分の PC の外で動かすときの選択肢と、3つの公式ドキュメントが共通して書いていることが分かります。

3つとも、開発サーバー(flask run・runserver・uvicorn の1プロセス)を本番では使わない、と公式が書いています。 Flask の公式ドキュメントは「本番とは開発環境ではないという意味で、利用者の数は関係ない」と説明しています。

選択肢 Flask FastAPI Django
アプリサーバー(公式が挙げるもの) Gunicorn・Waitress・mod_wsgi・uWSGI・gevent・ASGI 経由 fastapi run --workers N/uvicorn --workers N WSGI:Granian・Gunicorn・uWSGI・mod_wsgi。ASGI:Daphne・Granian・Hypercorn・Uvicorn
前に置くもの nginx・Apache(リバースプロキシ)+ProxyFix nginx 等か、PaaS の TLS 終端 nginx 等。静的ファイルは別途配信
コンテナ Dockerfile に gunicorn コンテナ1つに uvicorn 1プロセス(公式の推奨) Dockerfile に gunicorn か uvicorn
公開前の確認 SECRET_KEY・debug を切る HTTPS・環境変数 manage.py check --deploy・ALLOWED_HOSTS・DEBUG=False
PaaS(Flask 公式が挙げる例) PythonAnywhere・Google App Engine・Cloud Run・AWS Elastic Beanstalk・Azure。ほかに Railway・Render など
  • 手元では gunicorn(Flask・Django)と uvicorn の --workers 2(FastAPI)で起動し、前の節の速さを測りました。 gunicorn は「Booting worker with pid: …」をワーカーの数だけ出します。
  • FastAPI の公式は、Kubernetes などコンテナ基盤の上では --workers を使わず「コンテナ1つに Uvicorn 1プロセス」とし、複製は基盤側で行うのが基本だとしています。
  • Django は DEBUG = False にすると ALLOWED_HOSTS に無いホスト名からのアクセスを 400 にします(次の節に原文)。

Git から直接公開する PaaS の手順は、関連記事「Railwayとは|使い方・料金・デプロイ手順とエラー対処を実例で」で、DB と一緒にコンテナで動かす書き方は、関連記事「Docker Composeの使い方|compose.yamlの書き方と開発環境の作り方」で扱っています。

よくあるエラー・つまずき(原文と直し方)

この節では、3つのアプリを作る途中で実際に出したエラーの原文と、その直し方が分かります。

最初の1本で出るエラーの大半は、コードではなく「どこで・どの Python で・どのフォルダーから」起動したかが原因です。

出たもの(原文) 原因 直し方
ModuleNotFoundError: No module named 'django' venv を有効化していない・別の Python で起動した source venv/bin/activate か venv/bin/python manage.py …
Address already in use/Port 46101 is in use by another program.(Flask) 同じポートで前のサーバーが動いている 前のを Ctrl+C で止めるか --port を変える
ERROR: [Errno 98] error while attempting to bind on address ('127.0.0.1', 46102): address already in use(uvicorn) 同上 同上
Error: That port is already in use.(Django) 同上 同上
TemplateNotFound: 'index.html' not found in search path: 'templates'(jinja2.exceptions) 起動したフォルダーから見て templates/ が無い(FastAPI) main.py のあるフォルダーで起動するか、絶対パスを渡す
RuntimeError: Form data requires "python-multipart" to be installed. Form() を使うのに python-multipart が無い(pip install fastapi だけのとき) pip install python-multipart か "fastapi[standard]"
You have 19 unapplied migration(s). … Run 'python manage.py migrate' to apply them. migrate をまだ実行していない python manage.py migrate
RuntimeError: Model class items.models.Item doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS. settings.py の INSTALLED_APPS にアプリを足していない "items", を足す
Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS.(400) Django が知らないホスト名で来た ALLOWED_HOSTS に公開するホスト名を足す
CSRF verification failed. Request aborted.(403) Django のフォームに {% csrf_token %} が無い・curl で直接送った テンプレートに csrf_token を書く
405 METHOD NOT ALLOWED/{"detail":"Method Not Allowed"} POST 用の URL をブラウザで GET した(/add を直接開いた) フォームから送る。/add に GET を付けない
JSON の日本語が ããã²ã になる(FastAPI) curl -d 'name=たかひら' と URL エンコード無しで送った --data-urlencode を使う。ブラウザのフォームは自動でエンコードする

最後の文字化けは手元で実際に起きました。Flask は同じ送り方でも「たかひら」になり、FastAPI(Starlette の form 解析)だけが化けました。ブラウザからの送信では3つとも正しく保存されています。

  • 1venv の中の Python で起動しているかwhich python(Windows は where python)が venv の中を指す
  • 2app.py/main.py/manage.py のあるフォルダーで起動しているかtemplates/ と DB ファイルは起動した場所から探される
  • 3起動ログの URL をそのまま開いているかポートを変えたのにブラウザは前のポートのまま、が多い
  • 4Django はアプリを INSTALLED_APPS に足し、migrate したかモデルを変えるたびに makemigrations→migrate
  • 5エラー画面は開発サーバーのもので、本番では出ないかFlask の debug・Django の DEBUG は公開時に切る

よくある質問

Q. Python で Web アプリを作るには、何から始めればよいですか? A. venv を作り、pip install flask で Flask を入れ、この記事の app.py と index.html の2ファイルを置いて flask --app app run で起動し、ブラウザで 127.0.0.1:5000 を開きます。動いたら、フォームの項目や一覧の表示を変えてみるのが早い覚え方です。

Q. Flask・FastAPI・Django の違いを一言で言うと? A. 同梱の範囲です。Flask は最小、FastAPI は API に要るもの(型検査・/docs)、Django は Web サイトに要るもの(ORM・admin・認証・フォーム)が入っています。自分で書く行数は、手元のアプリではほぼ同じでした。

Q. 初心者はどれから始めるのがよいですか? A. 作りたいものが決まっていなければ Flask です。隠れている部分が少なく、HTTP・ルーティング・テンプレートの仕組みが見えます。作りたいものが API なら FastAPI、会員制サイトなら Django から始めても困りません。

Q. FastAPI は Flask より速いのですか? A. 手元の実測では、開発サーバー同士で 1,028 対 758 req/s、本番用サーバーで 1,845 対 1,391 req/s でした。差はありますが、DB の処理が重くなるほど縮まります。速さだけで選ぶ差ではありません。

Q. Django は遅いと聞きました。 A. runserver(開発サーバー)は手元で 306 req/s と最も遅い値でしたが、gunicorn に替えると 1,043 req/s になりました。遅いのは開発サーバーであって、本番用サーバーで動かせば他の2つと同じ桁です。

Q. Flask で DB を使うには何が要りますか? A. SQLite なら Python 標準の sqlite3 だけで動きます(この記事の app.py)。表が増えたら SQLAlchemy(Flask-SQLAlchemy)を足すと、Django の ORM に近い書き方になります。

Q. FastAPI で HTML の画面は作れますか? A. 作れます。pip install jinja2 と Jinja2Templates で、Flask と同じテンプレートがそのまま使えました。ただし FastAPI の強みは JSON の API なので、画面が中心なら Flask か Django のほうが道具が揃っています。

Q. Django の admin は何ができますか? A. モデルを admin.site.register(Item) と1行登録すると、ログイン付きの一覧・検索・追加・編集・削除の画面が付きます。業務の管理画面を一から作らずに済みます。

Q. 公開するには何を変えますか? A. 開発サーバーをやめて gunicorn か uvicorn で起動し、前に nginx か PaaS の TLS を置きます。Flask は debug を切り SECRET_KEY を環境変数に、Django は DEBUG = False・ALLOWED_HOSTS・check --deploy、FastAPI は HTTPS と --workers を公式の手順で設定します。

Q. 対応する Python の版は? A. 2026年10月29日時点で、Flask 3.1.3 は 3.9 以上、FastAPI 0.162.0 は 3.10 以上、Django 6.1 は 3.12〜3.14 です(pip のメタデータと Django 公式 FAQ)。3つを1つの venv で試すなら Python 3.12 以上にします。

Q. 3つを同時に学ぶべきですか? A. 1つで「フォーム→保存→一覧」まで作れれば、残りの2つは置き換えに近い作業です。この記事の3つのコードは同じ仕様なので、1つ作ったあとに残りを見比べると、違いが「同梱の範囲」だけだと分かります。

Q. Streamlit や Gradio はこの3つと何が違いますか? A. Streamlit・Gradio は「Python の関数に画面を自動で付ける」道具で、HTTP のルーティングやテンプレートを書きません。データの可視化や機械学習のデモには向きますが、URL ごとの画面や会員管理を作るものではありません。

まとめ

  • Python の Web アプリは、venv→pip install→1ファイル→起動→127.0.0.1 の流れで3つとも同じ。違いは同梱の範囲で、Flask<FastAPI<Django の順に多い。
  • 同じ「フォームで登録→一覧」を作ると、自分で書く行数は Flask 61・FastAPI 63・Django 54 行とほぼ同じ。Django はコマンドが 11 ファイルを作り、SQL を書かない。
  • 速さは開発サーバーで FastAPI 1,028・Flask 758・Django 306 req/s、本番用サーバーで 1,845・1,391・1,043 req/s。差の多くはサーバーの作りの差。
  • 最初の1本は、JSON の API なら FastAPI、ログイン・管理画面が要るなら Django、画面が数枚の小物や学習なら Flask。
  • つまずきの大半は venv・ポート・起動するフォルダー・INSTALLED_APPS・migrate。エラーの原文で見分けられる。

参考・出典

確認日はいずれも 2026年10月29日です。

  • Flask「Deploying to Production」:https://flask.palletsprojects.com/en/stable/deploying/
  • Flask「Quickstart」:https://flask.palletsprojects.com/en/stable/quickstart/
  • FastAPI「Server Workers - Uvicorn with Workers」:https://fastapi.tiangolo.com/deployment/server-workers/
  • FastAPI「Form Data」:https://fastapi.tiangolo.com/tutorial/request-forms/
  • FastAPI「Templates」:https://fastapi.tiangolo.com/advanced/templates/
  • Django「How to deploy Django」(6.1):https://docs.djangoproject.com/en/6.1/howto/deployment/
  • Django「Deployment checklist」(6.1):https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/
  • Django「FAQ: Installation」(対応する Python の版):https://docs.djangoproject.com/en/6.1/faq/install/
  • Python「venv — Creation of virtual environments」:https://docs.python.org/3/library/venv.html
  • Python「sqlite3 — DB-API 2.0 interface for SQLite databases」:https://docs.python.org/3/library/sqlite3.html
  • Gunicorn「Running Gunicorn」:https://docs.gunicorn.org/en/stable/run.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PythonでWebアプリを作る|Flask・FastAPI・Django実測比較」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?