mcpField
B
building-python-web-apps

building-python-web-apps

github.com/wdm0006開発10962 回インストール2 日前に更新
リポジトリ

building-python-web-apps について

Building Python Web Apps は、Python で小〜中規模の SaaS アプリを作る個人開発者や小規模チーム向けのスキルです。再利用しやすい FastAPI の構成、アプリ設計、認証、課金、バックグラウンド処理、デプロイの指針をまとめています。

主な機能

  1. 01FastAPI を中心としたアプリ設計
  2. 02ルート・サービス・モデルの階層化
  3. 03データベース・設定・認証のパターン
  4. 04Stripe 課金と Webhook の指針
  5. 05Docker・Terraform によるデプロイ設計

活用シーン

  1. 01小規模 SaaS アプリの立ち上げ
  2. 02アプリ構成と標準スタックの選定
  3. 03課金と Webhook 処理の実装
  4. 04デプロイとバックグラウンド処理の計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wdm0006/python-skills --skill building-python-web-apps

インストール後、Claude Code で/building-python-web-appsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

AUTH.md
BACKGROUND_JOBS.md
DEPLOYMENT.md
FRONTEND.md
PAYMENTS.md
STACK.md

詳細

Python Web アプリの構築

小規模から中規模の SaaS アプリを、個人または少人数のチームでリリースするための、独自の方針に基づく必要な機能をすべて備えた設計図です。定番でよく理解されている要素を、毎回同じ方法で組み合わせることを重視し、新しいアプリで「認証+課金+デプロイ」を数週間ではなく数時間で実現できます。

デフォルトのスタック

項目デフォルト
フレームワークFastAPI(非同期)、uvicorn[standard]
パッケージングuv + pyproject.toml + uv.lock、hatchling、src/ レイアウト
データベースPostgres 16 + asyncpg、SQLAlchemy 2.0 非同期、Alembic
設定pydantic-settings BaseSettings、環境変数プレフィックス、@lru_cache
決済Stripe ≥15、署名付き Webhook を 1 つ、冪等性テーブル
認証依存関係によるガード + bcrypt。トークン方式はアプリごとに決定(AUTH.md を参照)
フロントエンドJinja2 + Tailwind (CDN) + Alpine.js、または 分離された SPA
バックグラウンド処理cron-as-worker(同じイメージでコマンドを変更)— Celery は使いません
可観測性Sentry + PostHog。どちらもキーを設定するまで何もしません
デプロイDocker → Terraform 経由でマネージドプラットフォーム(Render 形式)
Lint/テストruff + pytest + pytest-asyncio、Makefile のエントリーポイント

変更する理由がなければ、各項目をそのまま使ってください。このスキルの残りの部分では「方法」を説明し、参照ファイルにはコピーして使えるテンプレートを用意しています。

パッケージ構成

レイヤー構造のパッケージを 1 つ用意し、ルーターをドメインごとに分割します。ビジネスロジックをルートに置くことはありません。

src/myapp/
  main.py            # app + middleware + include_router() in a loop
  config.py          # pydantic-settings Settings + get_settings()
  db.py              # lazy async engine/sessionmaker + get_session() dep
  models.py          # SQLAlchemy 2.0 DeclarativeBase, Mapped[]
  schemas.py         # Pydantic request/response models
  security.py        # hashing, tokens, auth dependencies
  observability.py   # Sentry/PostHog init (guarded)
  api/               # one APIRouter module per domain (+ deps.py, webhooks.py)
  services/          # business logic; external SDKs isolated here
migrations/versions/ # Alembic
tests/               # pytest, mirrors api/ + services/
terraform/           # render.tf, stripe.tf, posthog.tf, variables.tf
Dockerfile  docker-compose.yml  pyproject.toml  uv.lock  alembic.ini  Makefile

ルート → サービス → モデル。 ルートは解析と検証を行い、サービスを呼び出します。サービスはロジックを保持し、外部 SDK の呼び出し(Stripe、メール、LLM)をすべて担うため、モック可能です。モデルは永続化のみを担当します。

アプリの配線

モジュールレベルの app(ファクトリーでも構いませんが、必須ではありません)、ループ内で登録するルーター、ミドルウェアとしての横断的関心事、そして DB に到達可能であることを確認するヘルスチェックを用意します。完全なテンプレートは STACK.md を参照してください。

# main.py (shape)
app = FastAPI(docs_url=None if settings.is_prod else "/docs")
app.add_middleware(CORSMiddleware, allow_origins=settings.cors_origins, ...)
# request-id + security-headers middleware, slowapi rate limiter
for router in (auth.router, billing.router, webhooks.router, ...):
    app.include_router(router)

@app.get("/health")
async def health(db: SessionDep):
    await db.execute(text("SELECT 1"))   # 503 if the DB is down
    return {"status": "ok"}

依存関係は Annotated 型エイリアスとして共有すると、シグネチャがすっきりします。

# api/deps.py
SessionDep = Annotated[AsyncSession, Depends(get_session)]
UserDep    = Annotated[User, Depends(require_user)]

データベース

遅延初期化される非同期エンジンのシングルトン、pool_pre_ping=True、expire_on_commit=False、get_session() 非同期ジェネレーター依存関係、そしてプラットフォームが発行する postgres:// URL を postgresql+asyncpg:// に変換する URL 正規化処理を用意します。本番環境では Postgres、高速なローカル/CI テストでは SQLite(aiosqlite)を使います。 マイグレーションには Alembic を使い、デプロイの事前デプロイ手順で適用します。STACK.md を参照してください。

設定とシークレット

環境変数プレフィックスを持つ Settings(BaseSettings) を 1 つ用意し、@lru_cache get_settings() を使います。また、本番環境でデフォルトのシークレットまたはシークレットが未設定の場合に起動を拒否するバリデーターを設定します。.env.example はコミットし、.env は絶対にコミットしないでください。計算済みの @property ヘルパー(is_prod、cors_origins、billing_enabled)を使うと、ルートをすっきり保てます。

決済

Stripe では、署名検証を行う Webhook エンドポイントを必ず 1 つだけ設け、永続化されたイベントテーブルで冪等性を担保し、未知のイベントを受け取っても 500 エラーを返さないイベントタイプ別のディスパッチを実装します。SDK の呼び出しはすべて services/stripe/ モジュールに分離します。完全な実装パターン(チェックアウト、サブスクリプション、Webhook ハンドラー、Terraform でプロビジョニングする署名シークレット)については、PAYMENTS.md を参照してください。

実際に選択する項目

上記はすべて固定です。アプリごとに実際に選択するのは、次の項目です。

  • フロントエンド — サーバーサイドでレンダリングする Jinja + Tailwind/Alpine(JS のビルドが不要で、迅速にリリースできます) または 独自のオリジンで動作する、分離型の React/Vite SPA(より豊かな UI を実現できますが、構成要素が増えます)。 → FRONTEND.md
  • 認証方式 — cookie-JWT(ブラウザーアプリ)、bearer-JWT/API-key(プログラムからの利用)、 magic-link(パスワードレス)、または OAuth(委任型)。bcrypt と依存関係のガードは 共通で、異なるのはトークンのみです。→ AUTH.md
  • バックグラウンド処理 — リクエスト内でインライン実行(最もシンプル)、cron-as-worker(定期処理やキュー処理の デフォルト)、または Redis ベースのジョブキュー(ロングポーリングジョブ)。 これら3つでは対応できなくなった場合に限り、Celery を選択してください。→ BACKGROUND_JOBS.md
  • LLM 機能 — 構造化された Pydantic 出力を備えた pydantic-ai。起動時にキーが必要で、 モデルは環境変数で設定できます。補足は FRONTEND.md にあります。

デプロイ

単一の Docker イメージ(astral-sh/uv ベース、レイヤーキャッシュに対応した2段階の uv sync、非 root ユーザー、uvicorn --proxy-headers)。同じイメージを、異なるコマンドでウェブ、ワーカー、マイグレーションに使用します。マネージドプラットフォームを対象とし、Terraform(アプリ + Postgres + Stripe + PostHog をコードで管理)でプロビジョニングします。フォールバックとして、プラットフォームの Blueprint ファイルをドキュメントに記載します。マイグレーションはデプロイ前のステップで実行します。シークレットは Terraform で生成するか、同期対象外として指定し、別途設定します。→ DEPLOYMENT.md

レビューチェックリスト

Structure:
- [ ] Logic in services/, not in route handlers
- [ ] External SDKs (Stripe/email/LLM) isolated behind a service module
- [ ] /health (or /readyz) actually pings the database
- [ ] Job status: failure writes its own terminal value, "in progress" is written
      before the work is handed off, and the current value is read by monotonic id

Data:
- [ ] Async engine is a lazy singleton with pool_pre_ping
- [ ] postgres:// URLs normalized to postgresql+asyncpg://
- [ ] Every schema change has an Alembic migration

Config:
- [ ] Production refuses to boot on a default/missing secret
- [ ] CORS not "*" and HTTPS enforced in production
- [ ] .env.example committed; .env gitignored

Billing & security:
- [ ] Exactly one Stripe webhook, signature-verified, idempotent
- [ ] New passwords centrally limited to 72 UTF-8 bytes before bcrypt hashing
- [ ] Password verification remains unbounded for compatibility with existing hashes
- [ ] Passwords bcrypt-hashed; auth via reusable dependencies
- [ ] CSRF enforced globally for cookie auth (not per-form opt-in); no
      state-changing GET requests — mutations use POST/DELETE
- [ ] Client IP for rate-limit keys/allowlists/audit logs taken from a fixed
      position in the X-Forwarded-For chain (not the spoofable left-most value)
- [ ] Authentication rate limits use a required shared backend; backend outages
      fail closed with 503, while healthy exhausted buckets return 429 + actual TTL
- [ ] Private OpenAPI schema served only by a custom guarded route (`openapi_url=None`)
- [ ] Sentry + PostHog init guarded (no-op when unkeyed, never break a request)

Ship:
- [ ] One Docker image; web/worker/migrate are just different commands
- [ ] Migrations run pre-deploy, not at app startup in prod
- [ ] Infra is in Terraform; secrets are not committed

参考ファイル

  • STACK.md — pyproject、main.py、db.py、config.py、Makefile のテンプレート
  • PAYMENTS.md — Stripe のチェックアウトと冪等性を備えた webhook のパターン
  • AUTH.md — 4つのトークン方式と共通の依存関係ガード
  • BACKGROUND_JOBS.md — インライン実行、cron-worker、Redis キューの比較
  • FRONTEND.md — Jinja/Tailwind/Alpine と分離型 SPA の比較(+ LLM に関する補足)
  • DEPLOYMENT.md — Dockerfile、Terraform、マイグレーション、シークレット

コメント