← サイトトップ

BTC-001

バックエンドツールチェーン完全ガイド

作成: 2026-06-15 / カテゴリ: バックエンド技術 / 対象プロジェクト: health-moniter

health-moniter のバックエンドを支える 4 つの道具 FastAPI(API フレームワーク)・uvicorn(ASGI サーバー)・SQLite(組み込み DB と冪等な init_db)・plotly(可視化) を、役割の違いと相互の関係に重点を置いて図解する。これは フロントエンドツールチェーン(Node.js / Vite / React) の対になるバックエンド側のドキュメント。
【本ノートでの用語規約】
目次
  1. 全体像 ——health-moniter のデータフロー
  2. FastAPI ——API フレームワーク
  3. uvicorn ——アプリを動かす ASGI サーバー
  4. SQLite と init_db ——冪等性が核
  5. plotly ——データ可視化
  6. まとめ(疑問と答え)

1. 全体像 ——health-moniter のデータフロー

4 つの道具が「どこに位置し、何を受け渡すか」を 1 枚で押さえる。ブラウザ(React)からのリクエストを uvicorn が受け、FastAPI のロジックが SQLite と外部 API(Open-Meteo)からデータを集め、解析して plotly で可視化用のグラフを返す、という流れ。

ブラウザ React (Vite) port 5173 uvicorn(ASGI サーバー)port 8000 FastAPI app ルーティング / 検証 解析ロジック グラフ生成 SQLite health.db(症状記録) Open-Meteo API 気圧/気温/湿度 plotly 相関グラフ JSON HTTP JSON 応答 グラフを返す 起動時に init_db() で SQLite スキーマを用意(冪等)
図 1. health-moniter のデータフロー。ブラウザ(React) → uvicorn が包む FastAPI → SQLite / Open-Meteo → 解析 → plotly で可視化。🟢
道具一言でいうとhealth-moniter での役割
FastAPIアプリAPI のロジック本体症状記録 API(POST /api/symptoms 等)の定義・検証
uvicornサーバーアプリを動かすプロセスFastAPI app を実際に起動し HTTP を待ち受ける
SQLiteデータ1 ファイル完結の DB症状記録を health.db に永続化。起動時 init_db でスキーマ用意
plotly可視化グラフ描画ライブラリ症状レベル × 気象データの相関グラフ(Phase 3)

2. FastAPI ——API フレームワーク

FastAPI は Python の Web/API フレームワーク。ASGI 上で動き、関数にデコレータを付けるだけでルートを定義できる。 最大の特徴は pydantic による型定義から、入力バリデーションと API ドキュメント(/docs)が自動生成されること。

最小コード例 ——ルーティングと型バリデーション

# app/main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# pydantic モデル: 受け取る JSON の「型の設計図」
class Symptom(BaseModel):
    level: int        # 1〜5 の症状レベル
    note: str | None = None

# GET ルート: ヘルスチェック
@app.get("/api/health")
def health():
    return {"status": "ok"}

# POST ルート: 症状を記録。body は Symptom 型で自動検証される
@app.post("/api/symptoms")
def create_symptom(s: Symptom):
    # level が int でない / 範囲外 などは FastAPI が 422 で自動拒否
    return {"saved": True, "level": s.level}
受信した JSON {"level":"3"} pydantic 検証 "3" → int に変換OK 型/範囲が違えば 422 検証失敗 → 422 エラー内容を自動で返す ハンドラ関数 create_symptom(s) /docs 型から自動生成 Swagger UI
図 2. FastAPI の型主導フロー。pydantic モデル 1 つから、入力検証・型変換・422 エラー・/docs(Swagger UI)がすべて自動で導かれる。🟢

ポイントは 「型を書く=検証ルールとドキュメントを書く」こと。level: int と宣言すれば、文字列 "3" は int に変換され、変換できない値や範囲外は FastAPI が自動で 422 Unprocessable Entity として拒否する。手書きの if 文による検証が要らない。 health-moniter では症状記録 API(POST /api/symptoms など)をこの形で提供する。

3. uvicorn ——アプリを動かす ASGI サーバー

FastAPI で書いた appそれ自体ではポートを開かない。HTTP を受け取って app を呼び出す「サーバー」が別に必要で、それが uvicorn(ASGI サーバー)。 「FastAPI=アプリ本体」「uvicorn=それを走らせるエンジン」と切り分けて理解するのが肝心。

uvicorn(ASGI サーバー・プロセス) port 8000 を開き、HTTP リクエストを待ち受ける :8000 ソケット FastAPI app app.main:app (ルーティング処理) ASGI で app を呼ぶ 応答 ブラウザから
図 3. uvicorn は port 8000 のソケットを持つ「外側のプロセス」で、その中の FastAPI app(app.main:app)を ASGI 経由で呼び出す。app 単体はポートを開けない。🟢

起動コマンドと主要オプション

# app.main の中の app を、port 8000 で起動(開発時)
uvicorn app.main:app --reload --port 8000

# ── 引数の意味 ─────────────────────
# app.main:app  →  app/main.py の中の app オブジェクトを指す(モジュール:変数)
# --reload      →  ソースを保存するたびに自動再起動(開発用。本番では使わない)
# --port 8000   →  待ち受けるポート番号
開発時本番時
起動例uvicorn app.main:app --reloaduvicorn app.main:app --host 0.0.0.0 --workers 4(または gunicorn 経由)
--reload使う(保存で即反映)使わない(無駄な再起動・性能低下を防ぐ)
ワーカー数1(デバッグ容易)複数(CPU を使い切る)
前段不要Nginx 等のリバースプロキシを置くことが多い

「FastAPI を書き換えた/コマンドだけで動かしている」のように混同しやすいが、実際に OS から見えるプロセスは uvicorn 1 つで、FastAPI app はその内側で呼ばれるオブジェクト、という関係を押さえておくと、ログやプロセス監視の読み方が変わる。

4. SQLite と init_db ——冪等性が核

SQLite はサーバープロセスを持たない「組み込み型」DBで、DB 全体が 1 つのファイル(health-moniter では health.db)に収まる。Python では標準ライブラリ sqlite3 だけで使えるため追加インストールが要らない。

接続の基本 ——foreign_keys / row_factory

import sqlite3

def get_conn():
    conn = sqlite3.connect("data/health.db")
    # SQLite は既定で外部キー制約をオフにする → 明示的にオンへ
    conn.execute("PRAGMA foreign_keys = ON")
    # 行を「列名でアクセスできる」形(dict 風)で受け取る
    conn.row_factory = sqlite3.Row
    return conn

# conn.execute(...).fetchone()["level"] のように列名で読める

4.1 冪等(idempotent)とは何か

冪等とは「同じ操作を 1 回やっても 100 回やっても、最終的な状態が同じ」という性質。日常の例で言えば、エレベーターの「↑ボタン」は何回押しても呼び出し状態は 1 つだけ=冪等。一方「カウンターに +1 する」操作は押すたびに増えるので冪等ではない。

冪等でない: CREATE TABLE(IF NOT EXISTS なし) 起動前 テーブル作成 1 回目: OK OperationalError 2 回目: 既に存在 → 失敗 → 起動毎に呼べない 冪等: CREATE TABLE IF NOT EXISTS 起動前 テーブル作成 1 回目: 作る 何もしない(skip) 2 回目以降: 状態同じ → 起動毎に安全
図 4. 冪等性の比較。上段(IF NOT EXISTS なし)は 2 回目の実行で「既に存在」エラーになり起動毎に呼べない。下段(IF NOT EXISTS あり)は 2 回目以降「何もしない」ので、状態が常に同じ=冪等で、起動毎に安全に呼べる。🟢

4.2 init_db が冪等であるべき理由

init_db は「アプリ起動時にスキーマ(テーブル定義)を用意する」関数。ここで CREATE TABLE IF NOT EXISTS を使うと、 初回起動ではテーブルを作り、2 回目以降は何もしないため、何度呼んでも DB の状態は同じになる=冪等になる。

冪等だと何が嬉しいか。アプリは再起動・デプロイ・複数ワーカー起動などで init_db()何度も呼ばれうる。 冪等でなければ「2 回目の起動でクラッシュ」してしまう。冪等なら「テーブルが無ければ作る/あれば触らない」だけなので、 起動の度に無条件で呼んでも安全になり、「初回かどうかを気にする条件分岐」が不要になる。

# app/db.py
def init_db():
    conn = get_conn()
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS symptoms (
            id        INTEGER PRIMARY KEY AUTOINCREMENT,
            level     INTEGER NOT NULL,
            note      TEXT,
            recorded_at TEXT NOT NULL DEFAULT (datetime('now'))
        );
    """)               # IF NOT EXISTS が冪等性の要
    conn.commit()
    conn.close()

起動時(lifespan)に init_db を呼ぶ

from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.db import init_db

@asynccontextmanager
async def lifespan(app: FastAPI):
    init_db()      # 起動のたびに呼ぶ。冪等なので何度でも安全
    yield           # ここでアプリが稼働
    # (終了時のクリーンアップを書く場所)

app = FastAPI(lifespan=lifespan)
uvicorn 起動 app を読み込む lifespan 開始 init_db() を呼ぶ テーブル無し → 作成 初回起動 テーブル有り → skip 2 回目以降 アプリ稼働 どちらでも同じ状態
図 5. 起動シーケンス。lifespan で init_db() を呼ぶと、初回はテーブルを作り、2 回目以降は skip する。どちらの経路を通っても到達するアプリ稼働時の状態は同一(冪等)。🟢

5. plotly ——データ可視化

plotly はインタラクティブなグラフ(ズーム・ホバー表示など)を描くライブラリ。重要なのは 「Python の plotly」と「plotly.js(ブラウザで描く本体)」が同じ仕組みの表裏であること。 Python 側はグラフを JSON 仕様として組み立て、それを実際に画面に描くのはブラウザ上の plotly.js。

Python plotly グラフを組み立て FastAPI 内 図の JSON 仕様 data + layout API 応答で渡す plotly.js(ブラウザ) JSON を実際に描画 React ダッシュボード to_json() HTTP
図 6. plotly の Python ⇄ plotly.js 関係。Python 側は図を JSON 仕様(data + layout)として作り、ブラウザ上の plotly.js がその JSON を受け取って実際に描画する。🟢

最小コード例 ——相関グラフを JSON で返す

import plotly.express as px

@app.get("/api/chart/correlation")
def correlation_chart():
    # 例: 気圧 x 軸、症状レベル y 軸の散布図
    fig = px.scatter(df, x="pressure", y="level",
                     labels={"pressure": "気圧(hPa)", "level": "症状レベル"})
    # 図を JSON にして返す → ブラウザの plotly.js が描く
    return json.loads(fig.to_json())
plotlyChart.js
主な言語Python と JS の両対応(同じ図仕様)JavaScript 中心
Python 連携得意(サーバーで図を組み立て JSON 化)基本データだけ渡し JS 側で構成
機能の幅科学計算寄り。3D・統計図も豊富軽量。基本的なグラフが中心
向く場面解析結果を Python から描く本プロジェクト軽い UI グラフ

health-moniter では Phase 3 で「症状レベル × 気圧/気温/湿度」の相関グラフをダッシュボードに描く。 Python(FastAPI)側で解析しグラフ仕様を作り、React フロントエンドの plotly.js が描画する分担になる。 🟡 Phase 3 は計画段階のため、最終的な API 形状はプロジェクト進行で変わりうる。

6. まとめ(疑問と答え)

疑問答え
FastAPI だけでサーバーは動く?動かない。uvicorn(ASGI サーバー)が app を起動して初めて HTTP を受けられる
app.main:app の意味は?app/main.py の中の app という変数(モジュール:オブジェクト)を指す
--reload は本番でも使う?No。開発時のみ。本番は外して複数ワーカーで動かす
init_db を毎回呼んで壊れない?壊れない。CREATE TABLE IF NOT EXISTS で冪等だから何度呼んでも状態は同じ
SQLite に別途インストールは要る?不要。Python 標準ライブラリ sqlite3 で使える。DB は 1 ファイル
plotly のグラフは誰が描く?Python が JSON 仕様を作り、ブラウザの plotly.js が実描画する

関連項目