Apps

012-Login+Graph|Step4_React×FastAPI:FastAPIでバックエンドを構築する

はじめに

フロントエンドの準備が整ったら、次はバックエンドの構築です。

本記事では、PythonのFastAPIを用いてユーザー認証機能を実装する方法を解説します。

バックエンドAPIの基礎を学びながら、フロントエンドとの連携を見据えた設計を進めていきましょう。

環境構築|仮想環境とライブラリをインストール

FastAPI

フォルダ構成

TEST
---backend
------main.py
---frontend
---売上データ
------売上データ2020.csv
------売上データ2021.csv
------売上データ2022.csv

仮想環境作成

cd backend
python -m venv .venv

仮想環境に入る

.venv\Scripts\activate

ライブラリをインストール

pip install "fastapi[all]"
pip install pydantic
pip install sqlmodel
pip install passlib[bcrypt]

FastAPI起動

uvicorn main:app --reload

APIのドキュメント(Swagger UI)

http://127.0.0.1:8000/docs

設定|FastAPI の基本セットアップとテストAPI

まずは、FastAPIの基本を理解するために、シンプルなAPIを作成します。

コード

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

説明

  • FastAPI() でアプリインスタンスを作成。
  • @app.get("/") でルートエンドポイント / を定義し、シンプルなレスポンスを返す。
  • read_root 関数が実行されると {"message": "Hello, FastAPI!"} を返す。

実行方法

.venv\Scripts\activate
uvicorn main:app --reload
http://127.0.0.1:8000/
http://127.0.0.1:8000/docs
  1. pip install fastapi uvicorn を実行して FastAPI をインストール。
  2. uvicorn ファイル名:app --reload でサーバー起動。
  3. ブラウザで http://127.0.0.1:8000/ にアクセスすると "Hello, FastAPI!" が表示される。
小学生でも理解できるように説明してください。

小学生でもわかる FastAPI の使い方!

📌 FastAPI ってなに?

FastAPI(ファストエーピーアイ)は、Python というプログラミング言語で "Web アプリ" を作るための道具(フレームワーク)だよ!
とても速くて、使いやすいのが特徴なんだ。


🎯 ステップ 1: 基本のセットアップ

まず、「こんにちは!」 と言ってくれるシンプルなプログラムを作ってみよう!

📜 コード

from fastapi import FastAPI  # FastAPI を使えるようにする

app = FastAPI()  # アプリの本体を作る

@app.get("/")  # 「/」にアクセスしたときにこの関数が動く
def read_root():
    return {"message": "Hello, FastAPI!"}  # これが画面に表示される

🔍 コードの説明

💡 どうやって動くの?

1️⃣ FastAPI() でアプリの本体を作る。(先生みたいなもの!)
2️⃣ @app.get("/") で「/」(トップページ)にアクセスされたら、下の関数を動かすよ!
3️⃣ read_root() という関数が動くと、「Hello, FastAPI!」 というメッセージを返すよ!


🚀 実行する方法

プログラムを動かすには、次の手順でやってみよう!

✅ 1. FastAPI をインストール

まず、FastAPI を使えるようにしよう。
(プログラムを作るための道具をインストールするイメージ!)

パソコンの「コマンドプロンプト」や「ターミナル」にこれを入力してね:

pip install fastapi uvicorn

👉 これで FastAPI がインストールされるよ!


✅ 2. プログラムを起動

プログラムを動かすには、次のコマンドを入力しよう:

uvicorn main:app --reload

👉 main:app は、main.py というファイルの app を動かすよ!
👉 --reload をつけると、変更をすぐに反映できるよ!


✅ 3. ブラウザで確認

プログラムが動いたら、パソコンのブラウザ(Google Chrome など)で
次のアドレスにアクセスしてみよう:

http://127.0.0.1:8000/

👉 すると、画面に {"message": "Hello, FastAPI!"} と表示されるよ! 🎉


✅ 4. API の説明を見る

FastAPI は 「API の説明書」 も自動で作ってくれるよ!
次のアドレスにアクセスすると、どんな API があるか確認できるよ:

http://127.0.0.1:8000/docs

👉 かっこいい画面が出てくるよ!✨


🎉 まとめ

✅ FastAPI を使うと、簡単に Web API を作れる!
@app.get("/") でトップページを作る!
uvicorn main:app --reload でプログラムを動かす!
http://127.0.0.1:8000/ にアクセスすると、「Hello, FastAPI!」が見れる!


これが FastAPI の 「はじめの一歩」 だよ!
次は、もっと便利な API を作ってみよう! 🚀✨

設定|CORS(クロスオリジンリソース共有)の設定

フロントエンド(React)との通信を許可するためにCORS設定を追加します。

コード

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# CORS 設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # React のオリジンを許可
    allow_credentials=True,
    allow_methods=["*"],  # すべてのHTTPメソッドを許可
    allow_headers=["*"],  # すべてのHTTPヘッダーを許可
)

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI with CORS!"}

説明

  • CORSMiddleware を追加し、allow_origins=["http://localhost:3000"] で React からのリクエストを許可。
  • これをしないと、ブラウザのCORS制約でAPIがブロックされる。

実行方法

.venv\Scripts\activate
uvicorn main:app --reload
http://127.0.0.1:8000/
http://127.0.0.1:8000/docs
小学生でも理解できるように説明してください。

🌍 CORS(コース)ってなに?小学生でもわかる説明!


🎯 ステップ 2: CORS を設定しよう!

CORS(コース) っていうのは、Webサイトが他のサイトとデータをやり取りできるようにするルールのことだよ!


🧐 なんで CORS が必要なの?

たとえば、こんなふうに Web アプリを作ったとするよ:

フロントエンド(見た目の部分) → React(http://localhost:3000)
バックエンド(データを管理する部分) → FastAPI(http://127.0.0.1:8000)

このとき、フロントエンド(React)からバックエンド(FastAPI)にデータを送ろうとすると、「えっ!違う場所(オリジン)からのリクエストはダメ!」 ってブラウザがブロックしちゃうんだ。💥🚫


🚀 CORS を設定すると…?

「このサイト(React)からのリクエストは OK にするよ!」 って FastAPI に教えてあげれば、通信ができるようになるよ!


📜 CORS を設定するコード

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware  # CORSを使うための機能を追加

app = FastAPI()

# 🔥 CORS の設定を追加するよ!
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # React からのリクエストをOKにする
    allow_credentials=True,  # 認証情報を許可(ログインなど)
    allow_methods=["*"],  # すべての HTTP メソッドを許可(GET, POST など)
    allow_headers=["*"],  # すべてのヘッダーを許可
)

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI with CORS!"}  # 「CORS を設定したよ!」と返す

🔍 コードの説明

1️⃣ from fastapi.middleware.cors import CORSMiddleware → CORS を使えるようにする!
2️⃣ app.add_middleware(CORSMiddleware, allow_origins=...)どのサイトのリクエストをOKにするか 設定する!
3️⃣ allow_origins=["http://localhost:3000"]React(フロントエンド)からのリクエストを許可!
4️⃣ allow_methods=["*"]どんな方法(GET, POST など)でもOK!
5️⃣ allow_headers=["*"]すべてのヘッダー(追加情報)もOK!
6️⃣ @app.get("/")「CORS を設定したよ!」と表示する API を作る!


⚡ 実際に動かしてみよう!

✅ 1. FastAPI を起動する

コマンドを入力して、FastAPI を動かそう!

uvicorn main:app --reload

👉 これで FastAPI が動き始めるよ! 🚀


✅ 2. API を確認する

ブラウザで次のアドレスにアクセスしてみよう:

http://127.0.0.1:8000/

👉 すると、こんなメッセージが見えるよ!

{"message": "Hello, FastAPI with CORS!"}

✅ 3. API の説明を確認

CORS の設定をした FastAPI の API を 自動で作られた説明ページ で見ることができるよ!
次のリンクにアクセスしてみてね:

http://127.0.0.1:8000/docs

👉 かっこいい API の説明ページ が出てくるよ!✨


🎉 まとめ

CORS(コース) は「このサイトからのリクエストはOK!」って決めるルール
✅ CORS を設定しないと React(フロント)と FastAPI(バック)で通信できない!
allow_origins=["http://localhost:3000"]React の通信を許可!
uvicorn main:app --reloadFastAPI を動かして確認!


これで フロントエンド(React)とバックエンド(FastAPI)が仲良く通信 できるようになったね! 🎉
次は データのやり取り をやってみよう! 🚀✨

データベース設定|データベースのセットアップ

SQLiteデータベースを作成し、データモデルを定義します。

コード

#モジュールインポート
from sqlmodel import SQLModel, create_engine

#データベースの設定
DATABASE_URL = "sqlite:///sales_info.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})

# データベース作成関数
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

# アプリ起動時にデータベースを作成
@app.on_event("startup")
def on_startup():
    create_db_and_tables()

説明

  • SQLModel は SQLAlchemy と Pydantic の機能を統合したORM。
  • create_engine で SQLite データベースを作成。
  • create_db_and_tables() でテーブルを作成。
小学生でも理解できるように説明してください。

📖 ステップ 3: データベースのセットアップを小学生でもわかるように説明!

💡 「データベース」って何?
データベースは、たくさんのデータをしまっておく「大きなノート」みたいなもの だよ!📖✨
たとえば、お店の売上やユーザーの情報を保存しておく場所だよ!


📝 何をするの?

1️⃣ データベースを作る
2️⃣ データベースにつなぐ(設定)
3️⃣ アプリを動かしたときにデータベースを準備する


🔍 コードを見てみよう!

# モジュールインポート
from sqlmodel import SQLModel, create_engine

# データベースの設定
DATABASE_URL = "sqlite:///sales_info.db"  # データベースの名前を決める
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})  # データベースとつなぐ

# データベース作成関数
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)  # テーブル(データを入れる箱)を作る

# アプリ起動時にデータベースを作成
@app.on_event("startup")
def on_startup():
    create_db_and_tables()  # アプリを動かすときにデータベースも作る

🧐 ひとつずつわかりやすく解説!

① モジュールインポート(道具を用意する)

from sqlmodel import SQLModel, create_engine

🔹 「SQLModel」 は、データベースを簡単に扱うための道具 🛠️
🔹 「create_engine」 は、データベースとつなぐために使う!🔌


② データベースの設定(どこにデータをしまう?)

DATABASE_URL = "sqlite:///sales_info.db"

🔹 データベースの名前を決める!
👉 "sales_info.db" という名前のデータベースを作るよ!💾

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})

🔹 データベースとつながる準備をする!


③ データベースを作る関数

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

🔹 データを入れるための「箱(テーブル)」を作る関数! 📦


④ アプリを動かしたときにデータベースも作る

@app.on_event("startup")
def on_startup():
    create_db_and_tables()

🔹 「アプリをスタートするときに、データベースを作ってね!」という命令 🚀


✨ まとめ

データベースは「大きなノート」📖(データを保存する場所!)
「create_engine」でデータベースとつながる🔌
「create_db_and_tables()」でテーブル(データを入れる箱)を作る📦
アプリを動かしたときに、自動でデータベースを準備する!


これで小学生でもデータベースを作れるね!😆🎉

データベースモデル定義|ユーザーと売上データのモデル定義

データモデルを定義し、データを保存できるようにします。

コード

#モジュールインポート
from sqlmodel import SQLModel, Field
from typing import Optional

# モデル定義(SQLModel)
class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    username: str = Field(unique=True, index=True)
    password: str  # ハッシュ化されたパスワード

class Sales(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    year: int
    department: str
    sales: float

説明

  • table=True でデータベーステーブルとして定義。
  • Field() を使って id を主キーに設定。
  • usernameunique=True でユニーク制約をつけ、index=True で検索を高速化。
  • Sales クラスで売上データ(年・部門・売上額)を管理。
小学生でも理解できるように説明してください。

📌 ステップ 4: ユーザーと売上データを保存する準備をしよう!


💡 そもそも「データモデル」って何?

データモデルとは、データを整理して保存するための「設計図」のことだよ!🏗️

たとえば、お店の売上(うりあげ)をメモするとき、
「何年のデータ?」
「どの部署(ぶしょ)のデータ?」
「いくらの売上?」
って決めてから書くよね?

コンピューターの中でも、こんなふうに 決められた形(モデル) にそってデータを保存するんだ!


🛠️ データモデルを作るコード

# 📦 必要な道具(モジュール)を準備
from sqlmodel import SQLModel, Field
from typing import Optional  # ID を「なし」にできるようにするための道具

# 👤 ユーザーモデル(ログイン情報を保存)
class User(SQLModel, table=True):  # 🗃️ 「User」 というデータの箱(テーブル)を作るよ!
    id: Optional[int] = Field(default=None, primary_key=True)  # 🔑 ID(番号)をつける
    username: str = Field(unique=True, index=True)  # 📛 ユーザー名(かぶらないようにする)
    password: str  # 🔒 パスワード(暗号化して保存)

# 📊 売上データのモデル
class Sales(SQLModel, table=True):  # 🗃️ 「Sales」 というデータの箱(テーブル)を作るよ!
    id: Optional[int] = Field(default=None, primary_key=True)  # 🔑 ID(番号)をつける
    year: int  # 📅 何年のデータか?
    department: str  # 🏢 どの部署(ぶしょ)のデータか?
    sales: float  # 💰 売上の金額

🔍 コードの説明

👤 ユーザーモデル(User)

ログインする人の情報 を保存するよ!
id: Optional[int] = Field(default=None, primary_key=True)
➡️ ID(番号)をつける!(1, 2, 3... みたいな感じ)
username: str = Field(unique=True, index=True)
➡️ ユーザー名(名前)を保存する!(かぶらないようにする)
password: str
➡️ パスワードを保存する!(暗号化する)🔒


📊 売上モデル(Sales)

売上のデータ を保存するよ!
year: int
➡️ 何年のデータか?(たとえば 2025年)
department: str
➡️ どの部署のデータか?(「営業部」とか「マーケティング部」とか)
sales: float
➡️ いくら売れたか?(お金の金額)


📌 どうやって使うの?

1️⃣ ユーザー情報を保存!
👉 ユーザーを登録すると User に保存される!
(例)

idusernamepassword
1tanaka****
2yamada****

2️⃣ 売上データを保存!
👉 売上のデータが Sales に保存される!
(例)

idyeardepartmentsales
12024営業部1000000
22025開発部2000000

🎉 まとめ

「データモデル」 は、データを整理して保存する「設計図」🏗️
「User」 クラスで「ログインする人のデータ」を保存できる!👤
「Sales」 クラスで「売上データ」を保存できる!📊

これで データを保存する準備ができたよ! 🚀💾
次は データを実際に入れてみよう! 🎉

スキーマ|Pydantic スキーマの定義

リクエストデータのバリデーションを行うためのPydanticモデルを作成します。

コード

#モジュールインポート
from pydantic import BaseModel

# Pydanticスキーマ
class UserCreate(BaseModel):
    username: str
    password: str

class UserResponse(BaseModel):
    id: int
    username: str

    class Config:
        from_attributes = True  # ORM モードを有効にする

class SalesCreate(BaseModel):
    year: int
    department: str
    sales: float

説明

  • UserCreate はユーザー登録用リクエストスキーマ。
  • UserResponse はユーザー情報のレスポンススキーマ。
  • SalesCreate は売上データ登録用スキーマ。
  • Config.from_attributes = True を指定すると、ORMオブジェクトをPydanticモデルに変換可能。
小学生でも理解できるように説明してください。

📌 ステップ 5: データの「正しい形」をチェックしよう!


💡 そもそも「バリデーション」って何?

バリデーションとは、データが「正しい形かどうか」をチェックすることだよ!✅

たとえば、
名前に数字が入っていたら?123tanaka ❌)
売上データに「文字」が入っていたら?売上: "たくさん" ❌)
こんなデータが入ると、コンピューターが困っちゃうよね!😵💥

そこで Pydantic(ピーダンティック) を使うと、こういう「おかしなデータ」を防ぐことができるんだ!🚦✨


🛠️ データの形を決めるコード

# 📦 必要な道具(モジュール)を準備
from pydantic import BaseModel  # データの形を決めるための道具

# 👤 ユーザー登録用(データを受け取るとき)
class UserCreate(BaseModel):
    username: str  # 📛 ユーザー名(文字)
    password: str  # 🔒 パスワード(文字)

# 👤 ユーザー情報の返信用(データを送るとき)
class UserResponse(BaseModel):
    id: int  # 🔑 ユーザーのID(数字)
    username: str  # 📛 ユーザー名(文字)

    class Config:  
        from_attributes = True  # ORMのデータ(データベースの情報)を変換できるようにする

# 📊 売上データ登録用(データを受け取るとき)
class SalesCreate(BaseModel):
    year: int  # 📅 何年のデータか?(数字)
    department: str  # 🏢 どの部署のデータか?(文字)
    sales: float  # 💰 売上の金額(小数もOK)

🔍 コードの説明

👤 ユーザーのデータ

データを受け取るとき(UserCreate) ➡️ username(ユーザー名)と password(パスワード)が 文字 かチェックする!

データを送るとき(UserResponse) ➡️ id(ユーザーの番号)と username(名前)を 正しい形で送る

💡 class Config: from_attributes = True
➡️ データベースから情報を取り出しやすくする設定!


📊 売上のデータ

データを受け取るとき(SalesCreate)year数字 じゃないとダメ!
department文字 じゃないとダメ!
sales小数の数字 じゃないとダメ!(例:100000.5

💡 こうすることで、データが 間違った形で入るのを防ぐ ことができるよ!🚦✨


🎉 まとめ

Pydantic を使うと「データの形」をチェックできる!🧐
UserCreate / UserResponse / SalesCreate で、それぞれのデータの形を決める!📋
✅ **間違ったデータが入らないようにすることで、エラーを防げる!**🚀

これで データの安全管理ができるようになったよ! 🎉💾
次は、このデータを 実際に使ってみよう! 🎯

ユーティリティ(便利な)関数|パスワードのハッシュ化

パスワードを安全に保存するため、passlib を使ってハッシュ化します。

コード

#モジュールインポート
from passlib.context import CryptContext


# =====================
# 6. ユーティリティ関数(パスワードハッシュ & DBセッション)
# =====================

# セッション取得
def get_session():
    with Session(engine) as session:
        yield session

# パスワードハッシュ化の設定
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# パスワードハッシュ化
def hash_password(password: str) -> str:
    return pwd_context.hash(password)

# パスワード検証
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

説明

  • bcrypt を使ってパスワードをハッシュ化。
  • hash_password() でパスワードを安全に保存。
  • verify_password() でログイン時にパスワードを検証。
小学生でも理解できるように説明してください。

このコードを小学生でも理解できるように、わかりやすく説明しますね!😊


🔹 何をするコード?

  1. データベースを使う準備get_session
  2. パスワードを安全にするhash_password
  3. パスワードをチェックするverify_password

💡 ①「データベースを使う準備」

def get_session():
    with Session(engine) as session:
        yield session

🔹 何をする?
👉 データベース(お店のレジみたいなもの)を開いて、データを入れたり、取り出したりするための「部屋」を準備します!
👉 yield session は、「データベースを使うよ!はいどうぞ!」って感じです。


💡 ②「パスワードを安全にする」

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

🔹 何をする?
👉 CryptContext という道具を使って、パスワードを「秘密のカギ」に変えられるようにします。
👉 bcrypt という方法で暗号化します。(例:"apple123""d8f$gk2@!#k3" みたいに変える)


💡 ③「パスワードを暗号化する」

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

🔹 何をする?
👉 入力されたパスワードを**秘密のカギ(暗号)**に変えます!
👉 こうすることで、誰かが見ても本当のパスワードがわからなくなります。

📝 例

print(hash_password("apple123"))
# 出力例:"$2b$12$yW7jGcL2yxXp2LnO1zK.xe0g7eXkjD9GpXh7wZ"

(本当のパスワードが見えない!)


💡 ④「パスワードをチェックする」

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

🔹 何をする?
👉 入力されたパスワードが正しいかチェックします!
👉 plain_password(入力したパスワード)と hashed_password(暗号化されたパスワード)を比べて、
「正しければ True、間違ってたら False」 を返します!

📝 例

hashed_pw = hash_password("apple123")
print(verify_password("apple123", hashed_pw))  # ✅ 正しいので True
print(verify_password("orange456", hashed_pw)) # ❌ 間違いなので False

📝 まとめ

関数何をする?
get_session()データベースの「部屋」を準備する
hash_password("apple123")パスワードを暗号化する"apple123" → "d8f$gk2@!#k3"
verify_password("apple123", hashed_pw)入力したパスワードが正しいか確認するTrue(合ってる) or False(間違い)

これで、小学生でも自分でコードが書けるようになるはず!🚀✨

ユーザー関連のAPI|

コード

#モジュールインポート
from sqlmodel import Session, select
from fastapi import Depends, HTTPException



# =====================
# 7. ユーザー関連のAPI
# =====================
@app.post("/register/", response_model=UserResponse)
def register_user(user: UserCreate, session: Session = Depends(get_session)):
    existing_user = session.exec(select(User).where(User.username == user.username)).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="User already exists")
    hashed_password = hash_password(user.password)
    new_user = User(username=user.username, password=hashed_password)
    session.add(new_user)
    session.commit()
    session.refresh(new_user)
    return new_user

@app.post("/login/")
def login_user(user: UserCreate, session: Session = Depends(get_session)):
    db_user = session.exec(select(User).where(User.username == user.username)).first()
    if not db_user or not verify_password(user.password, db_user.password):
        raise HTTPException(status_code=401, detail="Invalid username or password")
    return {"message": "Login successful", "username": user.username}

説明

  1. get_session() でデータベースセッションを取得。
  2. register_user() でユーザー登録。
  3. ユーザー名が既に存在する場合は 400 Bad Request を返す。
小学生でも理解できるように説明してください。

このコードを 小学生でも理解できる ように わかりやすく 説明します!😊


🔹 何をするコード?

  1. ユーザー登録(register_user) 👤✅
    • 新しいユーザーをデータベースに保存する!
    • すでに登録されている場合はエラーを出す!
  2. ユーザーログイン(login_user) 🔑💻
    • ユーザー名とパスワードを確認して、正しければ「ログイン成功」と返す!
    • もし間違っていたら「ログイン失敗」とする!

💡 ①「ユーザー登録(register_user)」

@app.post("/register/", response_model=UserResponse)
def register_user(user: UserCreate, session: Session = Depends(get_session)):

🔹 何をする?
👉 /register/ というURLにアクセスすると、新しいユーザーを登録 できる!

名前説明
user: UserCreateユーザーの名前とパスワードを受け取る
session: Session = Depends(get_session)データベースを使う

🔹 すでに登録済みかチェック

existing_user = session.exec(select(User).where(User.username == user.username)).first()
if existing_user:
    raise HTTPException(status_code=400, detail="User already exists")

👉 session.exec(select(User).where(User.username == user.username)).first() は、
 「この名前のユーザーがすでにいるか?」 をデータベースで探す!

🔴 すでにいたら → エラー (400 Bad Request)


🔹 パスワードを暗号化して登録!

hashed_password = hash_password(user.password)
new_user = User(username=user.username, password=hashed_password)

👉 hash_password(user.password)パスワードを安全にする!
👉 new_user暗号化したパスワードで新しいユーザーを作る!


🔹 データベースに保存!

session.add(new_user)
session.commit()
session.refresh(new_user)
return new_user

session.add(new_user)新しいユーザーをデータベースに入れる
session.commit()保存ボタンを押す
session.refresh(new_user)新しいデータを更新
return new_user登録したユーザーを返す

📌 まとめると… 👉 すでに同じ名前のユーザーがいないなら、新しく登録する!


💡 ②「ユーザーログイン(login_user)」

@app.post("/login/")
def login_user(user: UserCreate, session: Session = Depends(get_session)):

🔹 何をする?
👉 /login/ にアクセスすると、ユーザーがログインできる!
👉 user: UserCreateユーザー名とパスワードを受け取る
👉 session: Session = Depends(get_session)データベースを使う


🔹 データベースからユーザーを探す

db_user = session.exec(select(User).where(User.username == user.username)).first()

👉 select(User).where(User.username == user.username)
  「この名前のユーザーがいるか?」 を探す!


🔹 パスワードチェック

if not db_user or not verify_password(user.password, db_user.password):
    raise HTTPException(status_code=401, detail="Invalid username or password")

👉 もしユーザーがいなかったら (not db_user) → エラー (401 Unauthorized)
👉 パスワードが間違っていたら (not verify_password(...)) → エラー (401 Unauthorized)


🔹 ログイン成功!

return {"message": "Login successful", "username": user.username}

ユーザー名とパスワードが正しいなら…
「ログイン成功!」のメッセージを返す


🔹 まとめ!

関数何をする?
register_user()新しいユーザーを登録する(パスワード暗号化)
login_user()ユーザー名とパスワードをチェックしてログインする

🌟 実際に動かしてみると?

📝 ユーザー登録

📩 リクエスト

POST /register/
{
  "username": "taro",
  "password": "1234"
}

✅ レスポンス

{
  "username": "taro",
  "password": "$2b$12$yW7jGcL2yxXp2LnO1zK.xe0g7eXkjD9GpXh7wZ"
}

👉 パスワードが暗号化されている! 🔒


📝 ログイン

📩 リクエスト

POST /login/
{
  "username": "taro",
  "password": "1234"
}

✅ 成功!

{
  "message": "Login successful",
  "username": "taro"
}

🔴 間違えたら?

{
  "detail": "Invalid username or password"
}

👉 パスワードを間違えるとエラー! 🚨


📌 まとめ(小学生向け)

このコードでできること!

  1. ユーザーを登録する!(すでに登録済みならエラー)
  2. パスワードを暗号化する!(見えないようにする)
  3. ユーザーがログインできる!(パスワードが合っていればOK)

これで、小学生でも 「ユーザー登録 & ログインAPI」 を理解できるはず!🚀✨

売上データ作成API (/sales/)作成

コード

# =====================
# 8. 売上データ関連のAPI
# =====================
@app.post("/sales/")
def create_sales(sales: SalesCreate, session: Session = Depends(get_session)):
    existing_sales = session.exec(
        select(Sales).where(
            (Sales.year == sales.year) &
            (Sales.department == sales.department) &
            (Sales.sales == sales.sales)
        )
    ).first()

    if existing_sales:
        raise HTTPException(status_code=400, detail="Duplicate sales data exists")

    sales_data = Sales(
        year=sales.year,
        department=sales.department,
        sales=sales.sales
    )
    session.add(sales_data)
    session.commit()
    session.refresh(sales_data)
    return sales_data

@app.get("/sales/")
def read_all_sales(session: Session = Depends(get_session)):
    sales_data = session.exec(select(Sales)).all()
    if not sales_data:
        raise HTTPException(status_code=404, detail="No sales data found")
    return sales_data

@app.get("/sales/{year}")
def read_sales_by_year(year: int, session: Session = Depends(get_session)):
    sales_data = session.exec(select(Sales).where(Sales.year == year)).all()
    if not sales_data:
        raise HTTPException(status_code=404, detail=f"No sales data found for year {year}")
    return sales_data

説明

  1. POST リクエストで yeardepartmentsales を受け取る(SalesCreate モデルを使用)。
  2. get_session でデータベースセッションを取得。
  3. 同じ yeardepartmentsales のデータが既に存在するかチェック。
  4. 重複データがある場合、400 Bad Request エラーを返す。
  5. 新しい売上データを作成し、データベースに保存。
  6. コミットして、作成した売上データを返す。
小学生でも理解できるように説明してください。

🔹 このコードは何をする?

これは 売上データを管理するAPI のコードです!💰📊
以下の3つの機能があります:

  1. 売上データを登録する(create_sales)
    • 新しい売上データを追加する
    • 同じデータがあればエラーを出す
  2. すべての売上データを取得する(read_all_sales)
    • データベースにある売上データを全部見る
    • データがないとエラーを出す
  3. 特定の年の売上データを取得する(read_sales_by_year)
    • 指定した年の売上データだけを取得する
    • データがなければエラーを出す

💡 ① 売上データを登録する(create_sales)

@app.post("/sales/")
def create_sales(sales: SalesCreate, session: Session = Depends(get_session)):

🔹 何をする?
👉 /sales/売上データを送ると登録 できる!
👉 sales: SalesCreate年・部署・売上額を受け取る!


🔹 データがすでにあるかチェック

existing_sales = session.exec(
    select(Sales).where(
        (Sales.year == sales.year) &
        (Sales.department == sales.department) &
        (Sales.sales == sales.sales)
    )
).first()

👉 「同じ年・部署・売上額のデータがある?」 を探す!

🔴 もしすでにデータがあったら?

if existing_sales:
    raise HTTPException(status_code=400, detail="Duplicate sales data exists")

「すでに同じデータがあるよ!」 とエラーを出す! 🚨


🔹 新しい売上データを作成 & 保存

sales_data = Sales(
    year=sales.year,
    department=sales.department,
    sales=sales.sales
)
session.add(sales_data)
session.commit()
session.refresh(sales_data)
return sales_data

新しいデータを作る!
データベースに保存する!
登録したデータを返す!

📌 まとめると… 👉 売上データを追加する!(すでにある場合はエラー)


💡 ② すべての売上データを取得する(read_all_sales)

@app.get("/sales/")
def read_all_sales(session: Session = Depends(get_session)):

🔹 何をする?
👉 /sales/ にアクセスすると、全部の売上データを見れる!


🔹 データを取得する

sales_data = session.exec(select(Sales)).all()

データベースにあるすべての売上データを取ってくる!


🔹 データがない場合はエラー

if not sales_data:
    raise HTTPException(status_code=404, detail="No sales data found")

データが1つもないなら「データがありません!」とエラーを出す! 🚨


🔹 データを返す

return sales_data

データをそのまま返す! 📊✨

📌 まとめると… 👉 データベースにある売上データを全部見る!(なかったらエラー)


💡 ③ 特定の年の売上データを取得する(read_sales_by_year)

@app.get("/sales/{year}")
def read_sales_by_year(year: int, session: Session = Depends(get_session)):

🔹 何をする?
👉 /sales/2023 のように 年を指定して売上データを取得!


🔹 指定した年のデータを取得

sales_data = session.exec(select(Sales).where(Sales.year == year)).all()

「指定した年のデータだけを取ってくる!」


🔹 データがない場合はエラー

if not sales_data:
    raise HTTPException(status_code=404, detail=f"No sales data found for year {year}")

その年のデータがなかったら「データがないよ!」とエラーを出す! 🚨


🔹 データを返す

return sales_data

データをそのまま返す! 🎯

📌 まとめると… 👉 特定の年の売上データを取得!(なかったらエラー)


🌟 実際に動かしてみる!

📝 1. 売上データを登録

📩 リクエスト

POST /sales/
{
  "year": 2023,
  "department": "IT",
  "sales": 5000000
}

✅ レスポンス

{
  "year": 2023,
  "department": "IT",
  "sales": 5000000
}

🔴 同じデータを登録しようとすると?

{
  "detail": "Duplicate sales data exists"
}

👉 「同じデータがあるよ!」とエラーが出る! 🚨


📝 2. すべての売上データを見る

📩 リクエスト

GET /sales/

✅ レスポンス

[
  {
    "year": 2023,
    "department": "IT",
    "sales": 5000000
  },
  {
    "year": 2022,
    "department": "Marketing",
    "sales": 3000000
  }
]

👉 すべての売上データが見れる! 🎉

🔴 データがない場合?

{
  "detail": "No sales data found"
}

👉 「データがないよ!」とエラーが出る! 🚨


📝 3. 2023年の売上データを見る

📩 リクエスト

GET /sales/2023

✅ レスポンス

[
  {
    "year": 2023,
    "department": "IT",
    "sales": 5000000
  }
]

👉 2023年のデータだけ見れる! 🎯

🔴 データがない場合?

{
  "detail": "No sales data found for year 2025"
}

👉 「その年のデータはないよ!」とエラーが出る! 🚨


📌 まとめ(小学生向け)

関数何をする?
create_sales()新しい売上データを登録する(同じデータならエラー)
read_all_sales()すべての売上データを取得する(データがなければエラー)
read_sales_by_year()特定の年の売上データを取得する(なければエラー)

これで、小学生でも 「売上データAPI」 を理解できるはず!🚀✨

動作確認

APIエンドポイントの動作確認

以下のように作成したAPIがそれぞれ表示されていれば、ひとまずOK。

最終コード:main.py
# =====================
# 1. 必要なモジュールをインポート
# =====================
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlmodel import SQLModel, create_engine, Field, Session, select
from pydantic import BaseModel
from passlib.context import CryptContext

# =====================
# 2. アプリの設定(FastAPI & CORS)
# =====================
app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # Reactのオリジン
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# =====================
# 3. データベース設定
# =====================

#データベースの設定
DATABASE_URL = "sqlite:///sales_info.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})

# データベース作成関数
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

# アプリ起動時にデータベースを作成
@app.on_event("startup")
def on_startup():
    create_db_and_tables()


# =====================
# 4. データベースモデル定義(SQLModel)
# =====================

# モデル定義(SQLModel)
class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    username: str = Field(unique=True, index=True)
    password: str  # ハッシュ化されたパスワード

class Sales(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    year: int
    department: str
    sales: float


# =====================
# 5. スキーマ(Pydantic)
# =====================

# Pydanticスキーマ
class UserCreate(BaseModel):
    username: str
    password: str

class UserResponse(BaseModel):
    id: int
    username: str

    class Config:
        from_attributes = True  # ORMモードを有効にする

class SalesCreate(BaseModel):
    year: int
    department: str
    sales: float


# =====================
# 6. ユーティリティ関数(パスワードハッシュ & DBセッション)
# =====================

# セッション取得
def get_session():
    with Session(engine) as session:
        yield session

# パスワードハッシュ化の設定
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# パスワードハッシュ化
def hash_password(password: str) -> str:
    return pwd_context.hash(password)

# パスワード検証
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)



# =====================
# 7. ユーザー関連のAPI
# =====================
@app.post("/register/", response_model=UserResponse)
def register_user(user: UserCreate, session: Session = Depends(get_session)):
    existing_user = session.exec(select(User).where(User.username == user.username)).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="User already exists")
    hashed_password = hash_password(user.password)
    new_user = User(username=user.username, password=hashed_password)
    session.add(new_user)
    session.commit()
    session.refresh(new_user)
    return new_user

@app.post("/login/")
def login_user(user: UserCreate, session: Session = Depends(get_session)):
    db_user = session.exec(select(User).where(User.username == user.username)).first()
    if not db_user or not verify_password(user.password, db_user.password):
        raise HTTPException(status_code=401, detail="Invalid username or password")
    return {"message": "Login successful", "username": user.username}



# =====================
# 8. 売上データ関連のAPI
# =====================
@app.post("/sales/")
def create_sales(sales: SalesCreate, session: Session = Depends(get_session)):
    existing_sales = session.exec(
        select(Sales).where(
            (Sales.year == sales.year) &
            (Sales.department == sales.department) &
            (Sales.sales == sales.sales)
        )
    ).first()

    if existing_sales:
        raise HTTPException(status_code=400, detail="Duplicate sales data exists")

    sales_data = Sales(
        year=sales.year,
        department=sales.department,
        sales=sales.sales
    )
    session.add(sales_data)
    session.commit()
    session.refresh(sales_data)
    return sales_data

@app.get("/sales/")
def read_all_sales(session: Session = Depends(get_session)):
    sales_data = session.exec(select(Sales)).all()
    if not sales_data:
        raise HTTPException(status_code=404, detail="No sales data found")
    return sales_data

@app.get("/sales/{year}")
def read_sales_by_year(year: int, session: Session = Depends(get_session)):
    sales_data = session.exec(select(Sales).where(Sales.year == year)).all()
    if not sales_data:
        raise HTTPException(status_code=404, detail=f"No sales data found for year {year}")
    return sales_data

-Apps