DjangoとFastAPIの違いは「フルスタックか、API特化か」と説明されることが多いものの、その差が工数として表れるのは、テーブル定義・マイグレーション・管理画面・ログインの4か所です。この記事では、同じ受注管理をDjango 6.1.3とFastAPI 0.162.7で組み、モデルの書き方から非同期のDBアクセス、両者を併用する構成までをコードと公式ドキュメントで比べます。最後に、受託案件でどちらを選ぶかを要件の条件から言い切ります。
まとめ:DjangoとFastAPIの違いをDB層と管理画面の組み方から言い切る
結論から書きます。社内の担当者が受注や顧客を画面で登録・修正する業務システムなら、Djangoを選びます。モデルを1つ書けば、マイグレーション・管理画面・ログイン・権限までが標準の部品でつながるためです。FastAPIで同じ状態を作るには、SQLAlchemy・Alembic・管理画面の部品・JWTとパスワードハッシュのライブラリを選んで組み合わせる作業が残ります。
FastAPIを選ぶのは、画面を持たないAPIが主役で、外部サービスへの待ち時間が長い呼び出しを多く抱える場合です。DjangoもDB操作の非同期版を持ちますが、公式ドキュメントは非同期モードではトランザクションがまだ動かないと明記しています。
両方の利点が欲しい案件では、Djangoの中に型ヒントでAPIを書けるDjango Ninjaを足す構成を先に検討します。FastAPIを別プロセスで立てて同じDBを共有する構成は、マイグレーションの持ち主を1つに決められる場合に限って採ります。
同じ受注管理をDjango 6.1とFastAPI 0.162で組むための検証環境を揃える
題材は、商品コードsku・数量qty・登録日時を持つ受注テーブル1つです。画面とAPIの両方から登録される前提で、フレームワークが肩代わりする範囲を比べます。
Python 3.12以上に揃えて両方の依存パッケージを版指定で入れる手順
2026年11月14日時点のPyPIで、Djangoの最新は6.1.3(11月3日公開)でPython 3.12以上、FastAPIの最新は0.162.7(11月13日公開)でPython 3.11以上を要求します。FastAPIは0.162.6からPython 3.10を切っており、同じ環境で両方を試すならPython 3.12以上に揃えるのが手早い方法です。
# Django側:本体とDjango Ninjaだけで、DB・管理画面・認証まで揃う
python -m venv .venv-dj
source .venv-dj/bin/activate
pip install "Django==6.1.3" "django-ninja==1.8.0"
# FastAPI側:ORM・マイグレーション・非同期ドライバを自分で選んで入れる
python -m venv .venv-fa
source .venv-fa/bin/activate
pip install "fastapi[standard]==0.162.7" "SQLAlchemy[asyncio]==2.1.8" "alembic==1.20.1" "asyncpg==0.32.0"
インストールする行数の差が、そのまま設計判断の数の差です。FastAPI側の4つは本記事の選択であって、FastAPIが指定しているものではありません。FastAPI公式のSQL Databasesの章は「FastAPIはSQLデータベースの利用を求めず、どのDBライブラリを使ってもよい」と書き、例には同じ作者のSQLModelを使っています。
受注テーブルを題材に両者で書く場所とファイル構成が変わる箇所
同じ受注管理を作るときに、どのファイルへ何を書くかを並べます。Django側はstartprojectとstartappが雛形を作り、FastAPI側は配置そのものを決めるところから始まります。
| 役割 | Django 6.1 | FastAPI 0.162(本記事の構成) |
|---|---|---|
| テーブル定義 | orders/models.py | models.py(SQLAlchemy 2.1) |
| マイグレーション | makemigrations / migrate | Alembicの初期化とenv.pyの設定 |
| 管理画面 | orders/admin.py(標準) | sqladminを追加するか自作 |
| ログインと権限 | django.contrib.auth(標準) | ユーザー表・PyJWT・pwdlibを自作で接続 |
| DBセッション | フレームワークが管理 | Dependsで1リクエスト1セッションを渡す |
FastAPIの行が長いのは欠点というより、選択の余地が残っている印です。ただし受託案件では、選んだ部品ごとに更新を追う負担が保守費用へ乗ります。Django自体の構成とMVTの考え方はMVTの仕組みと始め方をDjango 6.1で解説したDjangoの入門にまとめています。
models.pyとSQLAlchemy 2.1による受注モデル定義とマイグレーション比較
テーブルを作ってから変更を本番へ流すまでの手順は、両者で最も差が出る部分です。
Djangoのmodels.pyとmakemigrationsによる受注テーブル作成例
# orders/models.py(Django 6.1.3)
from django.db import models
class Order(models.Model):
sku = models.CharField(max_length=32)
qty = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
# settings.py の INSTALLED_APPS に "orders" を足してから実行する
# python manage.py makemigrations orders
# python manage.py migrate
makemigrationsがモデルとの差分からマイグレーションファイルを生成し、migrateがDBへ適用します。テーブル名は既定で「アプリ名_モデル名」のorders_orderになります。この名前は後の章の併用構成で効いてくるため、覚えておいてください。本番での適用順や巻き戻しはmakemigrationsからの流れと本番適用・巻き戻しを扱うDjango migrateの実務で扱っています。
FastAPI・SQLAlchemy 2.1・Alembicによる受注テーブルの作成手順
# models.py(SQLAlchemy 2.1.8)
from datetime import datetime
from sqlalchemy import String, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
sku: Mapped[str] = mapped_column(String(32))
qty: Mapped[int]
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
# alembic init migrations
# migrations/env.py で target_metadata = Base.metadata を設定し、alembic.ini に接続先を書く
# alembic revision --autogenerate -m "create orders"
# alembic upgrade head
手順の形はDjangoとほぼ同じでも、Alembicの初期化とenv.pyの編集は自分で行います。Alembic公式のAuto Generating Migrationsの章は、自動生成が検出できない変更(列名の変更など)があるため、生成されたファイルを必ず読んで直すことが前提の説明です。FastAPI公式のSQL Databasesの章も、起動時にcreate_allでテーブルを作る例は手軽さのためで、本番ではアプリ起動前に流すマイグレーションを使い、当面はAlembicを直接使うよう書いています。
DjangoのPositiveIntegerFieldのような「正の整数」の制約は、SQLAlchemy側ではCheckConstraintを自分で書くか、APIの入口でPydanticのField(ge=1)に任せるかを決める必要があります。どちらに置くかは画面からの登録経路があるかで変わり、ここも設計判断が1つ増える箇所です。
管理画面と認証をDjango標準とFastAPI側の追加部品で揃える工数の差
業務システムの見積もりで差が開くのは、APIよりも社内担当者が触る画面と、その入口のログインです。
Django adminに受注モデルを登録して社内向けの一覧画面を出すコード
# orders/admin.py
from django.contrib import admin
from .models import Order
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ("id", "sku", "qty", "created_at")
list_filter = ("created_at",)
# python manage.py createsuperuser で管理者を作り、/admin/ にログインする
これで一覧・絞り込み・登録・編集・削除の画面と、ユーザーごとの権限設定が動きます。ただしDjango公式のadminの解説は、推奨用途を組織内部の管理ツールに限り、フロントエンド全体を作るためのものではないと明記しています。取引先や一般会員に開く画面は別に作る前提です。ModelAdminのカスタマイズと権限設計はadmin標準の範囲とModelAdmin・権限の実装を扱うDjango管理画面の設計を参照してください。
FastAPIで管理画面を作るときにsqladminを足すか自作するかの判断
FastAPI本体に管理画面はありません。SQLAlchemyのモデルから管理画面を作る部品としてsqladmin(SQLAlchemy Admin for Starlette/FastAPI)があり、PyPIの最新は0.32.0(2026年9月20日公開)です。モデルごとにModelViewを定義して登録する流れはDjango adminに近いものの、0系の部品であり、ログインの仕組みも自分でつなぎ込みます。
判断の基準は、管理画面の利用者と画面数です。開発者と少数の運用担当だけが触り、一覧と編集で足りるならsqladminで十分です。担当者が10人を超え、部署ごとに見せる項目を変える必要がある場合は、FastAPIにこだわる理由が薄れます。その要件はDjango adminの権限とグループが最初から引き受ける範囲です。
ログインとユーザー管理をdjango.contrib.authとPyJWTで組む差
Django公式のUser authenticationのとおり、Djangoはユーザー・グループ・権限のテーブル、パスワードのハッシュ化、ログイン画面のビューを標準で持ちます。一方FastAPI公式のOAuth2 with JWTの章は、PyJWTとpwdlib[argon2]を追加し、ユーザーは例として辞書fake_users_dbに置いています。実案件で自分で揃える必要があるのは、次の項目です。
| 要素 | Django 6.1 | FastAPI 0.162 |
|---|---|---|
| ユーザーと権限のテーブル | 標準(auth_user ほか) | モデルとマイグレーションを自作 |
| パスワードのハッシュ | 標準 | pwdlib 0.3.1(Argon2) |
| トークン発行と検証 | セッション標準・トークンはDRF等 | PyJWT 2.15.1で発行と検証を自作 |
| パスワード再設定画面 | 標準のビューあり | メール送信を含めて自作 |
トークンの失効や再設定の流れは、自作すると漏れが出やすい箇所です。Django側の標準認証で足りる範囲とカスタムユーザーを先に切る判断は標準authの範囲とカスタムユーザー・allauthを扱うDjangoのログイン機能にまとめています。
非同期のDBアクセスをDjangoのa付きメソッドとAsyncSessionで動かす
「FastAPIは非同期、Djangoは同期」という区分は、Django 4.1で非同期のORMメソッドが入ってから正確ではなくなりました。差は書けるかどうかではなく、トランザクションの扱いにあります。
Djangoのafirstで書く非同期ビューとトランザクションの制約
# orders/views.py(Django 6.1.3)
from asgiref.sync import sync_to_async
from django.db import transaction
from django.http import JsonResponse
from .models import Order
async def latest_order(request):
order = await Order.objects.order_by("-created_at").afirst()
if order is None:
return JsonResponse({"detail": "not found"}, status=404)
return JsonResponse({"id": order.id, "sku": order.sku, "qty": order.qty})
def _create_order(sku, qty):
# トランザクションが要る処理は同期関数にまとめる
with transaction.atomic():
return Order.objects.create(sku=sku, qty=qty)
async def create_order_async(sku, qty):
return await sync_to_async(_create_order)(sku, qty)
Django公式のAsynchronous supportの章によれば、SQLを発行するQuerySetのメソッドにはafirst()やacreate()のようにaを付けた非同期版があります。一方で「Transactions do not yet work in async mode.」とあり、トランザクションが要る処理は1つの同期関数にまとめてsync_to_async()で呼ぶよう勧めています。在庫の引当と受注登録を同時に確定させるような業務処理は、この形で記述する必要がある処理です。
同じ章は、WSGIで動かしてもasyncビューは動くものの、長時間のリクエストを効率よく扱う利点は得られず、その利点にはASGIでの配備が必要だとしています。WebSocketや長時間接続が主役なら、Django ChannelsやFastAPIの検討に進む場面です。
FastAPIでAsyncSessionとasyncpgを使う構成とdefで書く選択肢
# main.py(FastAPI 0.162.7 / SQLAlchemy 2.1.8)
import os
from contextlib import asynccontextmanager
from typing import Annotated
from fastapi import Depends, FastAPI
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from models import Order
# 例:postgresql+asyncpg://app:パスワード@localhost/app
engine = create_async_engine(os.environ["DATABASE_URL"])
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
@asynccontextmanager
async def lifespan(app: FastAPI):
yield
await engine.dispose() # 終了時に接続プールを閉じる
app = FastAPI(lifespan=lifespan)
async def get_session():
async with SessionLocal() as session:
yield session
SessionDep = Annotated[AsyncSession, Depends(get_session)]
class OrderIn(BaseModel):
sku: str = Field(min_length=1, max_length=32)
qty: int = Field(ge=1)
@app.post("/orders", status_code=201)
async def create_order(body: OrderIn, session: SessionDep):
async with session.begin(): # ブロックを抜けるとコミットされる
order = Order(sku=body.sku, qty=body.qty)
session.add(order)
return {"id": order.id}
SQLAlchemy 2.1のasyncio拡張では、async with session.begin()の中でトランザクションを非同期のまま書けます。Djangoとの差はここです。起動・終了の処理は、Starlette 1.0でon_eventが削除された経緯もあり、lifespanで書きます。Dependsでセッションを渡す仕組みとテストでの差し替えは依存性注入・yield・テストの差し替えをコードで解説したDependsの使い方を参照してください。
非同期ドライバを入れずに構成する方法も選択肢の1つです。同期のSessionを使い、パス関数をdefで書けば、FastAPIは外部のスレッドプールで実行します。社内のDBだけを相手にするAPIなら、この構成のほうが事故は少なくなります。
DjangoとFastAPIを1つのシステムで併用する2つの構成と注意点
「管理画面はDjangoで、APIは型ヒントで書きたい」という要望は珍しくありません。実現の仕方は2つあり、勧める順に並べます。
Django Ninjaで型ヒントのAPIをDjangoプロジェクトの中に足す構成
# config/api.py(django-ninja 1.8.0)
from ninja import NinjaAPI, Schema
from orders.models import Order
api = NinjaAPI()
class OrderIn(Schema):
sku: str
qty: int
@api.post("/orders")
def create_order(request, payload: OrderIn):
order = Order.objects.create(**payload.model_dump())
return {"id": order.id}
# config/urls.py に path("api/", api.urls) を足すと /api/docs に仕様画面が出る
Django Ninjaの公式サイトは、DjangoとPythonの型ヒントでAPIを作るフレームワークで、PydanticとOpenAPIに基づくと説明しています。モデル・管理画面・認証はDjangoのまま、APIの書き味だけをFastAPIに近づける構成です。プロセスもマイグレーションも1つで済むため、併用を考えたら最初にこの構成を当てます。DRFとの違いと実行結果は使い方とDRFとの違いを実行結果で解説したDjango Ninjaの記事で扱いました。
管理画面はDjango、公開APIはFastAPIで同じDBを共有する構成
公開APIだけ負荷や配備の単位を分けたい場合は、FastAPIを別プロセスで立て、同じDBを読み書きさせます。このとき、マイグレーションの持ち主はDjangoだけにします。FastAPI側のSQLAlchemyモデルはDjangoが作ったテーブルに合わせ、Alembicの自動生成は同じテーブルに向けません。
# FastAPI側:Djangoが作ったテーブルを読み書きするだけのモデル
class Order(Base):
__tablename__ = "orders_order" # Djangoの既定「アプリ名_モデル名」に合わせる
id: Mapped[int] = mapped_column(primary_key=True)
sku: Mapped[str] = mapped_column(String(32))
qty: Mapped[int]
created_at: Mapped[datetime]
FastAPI公式のIncluding WSGIの章は、a2wsgiのWSGIMiddlewareでWSGIアプリをFastAPIの配下にマウントする方法を示し、fastapi.middleware.wsgi版は非推奨としています。ただしDjangoをURLの接頭辞の下に置くと、URL生成や静的ファイルの設定を合わせ込む作業が必要です。受託案件ではマウントせず、リバースプロキシのパス振り分けで2プロセスに分ける構成を基本にしています。
DjangoとFastAPIのどちらで受託案件を組むかを要件の条件から言い切る
ここまでの差を、選ぶための条件に落とします。性能の評判ではなく、画面の量・認証の要件・保守体制で判定します。
Djangoを選ぶ条件と5.2 LTSか6.1かを保守期間から決める基準
Djangoを選ぶのは、社内担当者が使う登録・編集の画面と、部署ごとの権限が要件の半分以上を占める案件です。受注管理・顧客管理・申請ワークフローの多くがここに入ります。FastAPIで組むと、前の章で並べた管理画面と認証の部品選びと接続がそのまま見積もりに乗ります。
版の選び方にも注意が要ります。Django公式のダウンロードページのサポート表では、6.1の延長サポートは2027年12月まで、5.2 LTSは2028年4月までです。最新の6.1を選ぶと、LTSの5.2より先にサポートが切れる状況です。年次の更新作業を契約に入れられない案件なら5.2 LTSで始め、入れられるなら6.1で始めて次のLTSへ上げる計画を立てます。将来性の数値の参照先は、FastAPI台頭後の採用条件を数値で判断したDjangoの将来性です。画面とAPIをまとめて要件から設計する場合は、業務に合わせて一から組むフルスクラッチ開発で相談を受けています。
FastAPIを選ぶ条件と0系のバージョン固定を保守に組み込む段取り
FastAPIを選ぶのは、画面を持たないAPIが主体で、外部APIや生成AIの呼び出しなど待ち時間の長い処理を束ねる案件です。管理画面が一覧と編集だけで足りるなら、sqladminを足しても工数は抑えられます。逆に、ユーザーの権限やパスワード再設定まで要るのに「速いから」でFastAPIを選ぶのは見送ります。認証の自作部分が、性能差より大きな保守リスクになるためです。
# requirements.txt(0.162系のPATCH更新だけを受け入れる)
fastapi[standard]>=0.162.7,<0.163.0
SQLAlchemy>=2.1.8,<2.2
alembic>=1.20.1,<1.21
FastAPI公式のAbout FastAPI versionsの章は、0系のためMINOR更新で破壊的変更が入りうると明記し、MINORの範囲で固定する書き方を例示しています。ディレクトリ構成とSQLAlchemyの設計はディレクトリ構成とSQLAlchemy 2.1の設計を実測で解説したFastAPIのベストプラクティス、本番のワーカー構成はGunicornとの違いと本番設定を扱うUvicornの解説が出発点になります。
よくある質問
DjangoとFastAPIの比較で検索されることの多い質問に、2026年11月時点の版で答えます。
DjangoとFastAPIはどちらが処理は速いのですか?
外部APIの呼び出しなど待ち時間の長い処理を非同期で束ねるAPIでは、ASGIを前提に作られたFastAPIのほうが同時に捌ける数で有利です。一方、DB問い合わせが支配的な業務画面では、フレームワーク本体の差よりクエリの書き方とインデックスの影響が大きく出ます。速度だけで選ぶと、管理画面と認証を自作する工数で逆転するため、要件から判断します。
FastAPIからDjangoのORMを呼び出して使えますか?
技術的には、Djangoの設定を読み込んでからモデルを呼べば使えます。ただしasync defの中で同期のORMを呼ぶとSynchronousOnlyOperationのエラーになり、a付きの非同期メソッドでもトランザクションは使えません。DjangoのORMを使いたい理由があるなら、FastAPIに寄せるよりDjango Ninjaを使う構成のほうが素直です。
DjangoでもFastAPIのように型ヒントでAPIを書けますか?
Django Ninjaを使えば書けます。2026年11月時点の最新は1.8.0で、Pydanticのスキーマで入力を検証し、OpenAPIの仕様画面も自動で出ます。従来のDjango REST Frameworkはクラスベースのシリアライザで書く方式で、最新は3.18.3です。DRFの構成は3.18の最小構成と本番前に変える設定を扱うDRFの解説を参照してください。
DjangoのアプリをFastAPIへ移行するべきですか?
管理画面と認証を使っているDjangoアプリなら、移行は勧めません。移した時点で、その2つを作り直す工数が発生するためです。APIの一部だけに高い同時接続が求められる場合は、その部分だけをFastAPIの別プロセスに切り出し、DBのマイグレーションはDjango側に残す構成を採ります。
FastAPIとFlaskとDjangoの3つで迷ったときはどう絞り込みますか?
最初に、社内向けの管理画面と権限が要るかで分けます。要るならDjango、要らずAPIが主役ならFastAPIかFlaskです。後者の2つの比較は、入力検証と仕様書の出し方の差が決め手になります。詳しくは同じAPIを両方で書いて比べたFlaskとFastAPIの違いで扱っています。
関連記事
- FastAPIとは?最短の起動手順とasync/defの使い分け、0.162の変更点:FastAPI単体の入門と起動手順です。
- DjangoとFlaskの違い|内蔵範囲・非同期・案件規模から選ぶ判断基準:Djangoを軸にFlaskとFastAPIを振り分ける判断材料です。
- FlaskとFastAPIの違い|同じAPIを両方で書いて型検証・非同期・OpenAPIを比較:入力検証とOpenAPIの差をコードで比べています。
- Django Ninjaとは?使い方とDRFとの違いを1.7.3の実行結果で解説:Djangoの中で型ヒントのAPIを書く方法です。
- Djangoの将来性を2026年8月の数値で判断|FastAPI台頭後の採用条件:長期保守でDjangoを採る条件です。