- Python
PythonでWebアプリを作る|Flask・FastAPI・Django実測比較
結論
Python で Web アプリを作るなら、画面中心の小さなものは Flask、JSON を返す API が中心なら FastAPI、ログイン・管理画面・DB まで最初から揃えたいなら Django です。3つとも「venv を作る→pip で入れる→1ファイル書く→起動→ブラウザで開く」の流れは同じで、違いは「どこまで最初から入っているか」です。
この記事でわかること
- Flask・FastAPI・Django の違いと、最初の1本をどれで作るかの判断表
- 同じ「フォームで登録→一覧」アプリを3つで作った全コード・起動ログ・画面
- コード量・依存パッケージ数・速さ(req/s)・エラー原文の実測
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 に直接入れると、別のプロジェクトと版がぶつかります。
仮想環境を作る
python3 -m venv venv。Windows は py -m venv venv。有効化は Linux・macOS が source venv/bin/activate、Windows が venv\Scripts\activate。
フレームワークを入れる
pip install flask/pip install "fastapi[standard]"/pip install django。3つ試すなら1つの venv に全部入れても動く(この記事はそうした)。
1ファイル書いて起動する
次の節のコードを置き、flask run・uvicorn main:app・python manage.py runserver のどれかで起動。
ブラウザで 127.0.0.1 を開く
http://127.0.0.1:ポート/。起動ログに出る URL をそのまま開く。
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回流し、中央の回の値です。
| 起動のしかた | 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 に値を入れるとき
f"INSERT INTO items (name) VALUES ('{name}')" のように文字列で組み立てる。入力に ' が入ると壊れ、SQL インジェクションの入口になるexecute("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返すのが JSON だけ(画面は別のフロントや他のアプリ)→ FastAPI型で入力を検査し、/docs で試せる。機械学習モデルの API もここ
- 2ログイン・会員・管理画面・複数テーブルが要る → Djangoauth・admin・ORM・migrate を自分で組まずに済む
- 3画面が数枚で DB は1〜2表、とにかく早く動かしたい → Flask1ファイルから始め、必要になったら拡張を足す
- 4Web の仕組み(HTTP・ルーティング・テンプレート)を学ぶのが目的 → Flask隠れている部分が少なく、何が起きているか追いやすい
- 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 を使う |
Flask を選んだら次に
Blueprint で画面を分ける。DB が増えたら SQLAlchemy、フォームが増えたら Flask-WTF、ログインは Flask-Login を足す。
足し算で育てる
FastAPI を選んだら次に
pydantic のモデルで入出力を固定し、Depends で DB 接続と認証を共通化する。画面が要るなら Jinja2 か別のフロント。
型を先に決める
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期連続の黒字決算。