FastAPIとSQLAlchemyを使って、10分でREST APIを作ってみた

# はじめに

こんにちは！KIYOラーニングでスタディングの開発をしております @Kumacchiino です。
普段の業務では、PHPやJavaScriptを使うことが多いのですが、AI関連のプロジェクトではPythonを触ることもあり、そこで得たナレッジを備忘録としてまとめていけたら、と思います。

今回は、FastAPIとSQLAlchemyを使って、ユーザ情報を扱うAPIを作って行こうと思います。

## この記事を読むと

* FastAPIとSQLAlchemyを使って、シンプルなREST APIを作成出来るようになる

### 対象読者

* Pythonの基本的な知識がある人
* フレームワークを使って、APIを作りたい人

### 前提条件

* Python環境がローカルに構築されていること

## FastAPIとは

Python製の軽量かつ高速なWebフレームワークです。
型安全性や自動APIドキュメント生成が特徴。開発効率とパフォーマンスの両方を重視するモダンな選択肢になっています。学習コストも低めです。

[公式ドキュメント](https://fastapi.tiangolo.com/ja/)

## SQLAlchemyとは

Python向けのSQL操作用ライブラリです。
SQLを書かなくても、抽象的にデータベースにアクセスできて、Python界隈ではデファクトスタンダード的な位置づけになっています。（所謂ORMツール）

## プロジェクト作成

まずは、任意のディレクトリにプロジェクトディレクトリを作成します。
今回は、`api`というプロジェクトディレクトリを作成して、この中にAPIを実装していこうと思います。

```bash
mkdir -p api
cd api
```

その後に、venvというツールを使って仮想環境を作成して、pip経由で必要なパッケージをインストールします。

venvで仮想環境を作ることで、プロジェクトごとに独立したPython環境を構築でき、ライブラリのバージョン管理や依存関係の競合を防ぎ、何故か動かない、エラーが出る、という可能性が無くなります。

```bash
python3 -m venv venv
source venv/bin/activate
pip install fastapi SQLAlchemy
```

## ファイルを作成

プロジェクトディレクトリ直下で、以下のコマンドを入力して、それぞれ必要なディレクトリとファイルを作成します。

```bash
mkdir -p app/models app/schemas app/crud app/routers
touch app/__init__.py app/main.py app/database.py
touch app/models/__init__.py app/models/user.py
touch app/schemas/__init__.py app/schemas/user.py
touch app/crud/__init__.py app/crud/user.py
touch app/routers/__init__.py app/routers/user.py
```

上記のファイルを作成後のディレクトリ構成は以下のようになります。

```bash
└── app
    ├── __init__.py
    ├── crud
    │   ├── __init__.py
    │   └── user.py
    ├── database.py
    ├── main.py
    ├── models
    │   ├── __init__.py
    │   └── user.py
    ├── routers
    │   ├── __init__.py
    │   └── user.py
    └── schemas
        ├── __init__.py
        └── user.py
```

## 各ファイルについて

まず、`__init__.py`については、ディレクトリを Python のパッケージとして認識させるためのものです。
このファイルがあることで、各ファイル内でimport出来るようになります。

次にエントリーポイントである`app/main.py`についてです。


```app/main.py
from fastapi import FastAPI
from app.database import engine, Base
from app.routers import user

Base.metadata.create_all(bind=engine)

app = FastAPI(
    title="FastAPI REST API",
    description="A REST API",
    version="1.0.0"
)

app.include_router(user.router)
```

ここでは、アプリケーションの基本情報（タイトル、説明、バージョン）を設定しています。
`app/database.py`で定義されたエンジンを用いて、ORMモデルに対応するテーブルが存在しなければ作成します。

また、userのルーターをinclude_routerを使って登録しています。
これにより、各エンドポイントがFastAPIアプリケーションに追加されます。

次に、`app/database.py`のコードについてです。

```app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
```

最初に、SQLiteを使うための接続文字列を定義しています。
ファイル形式のDBをプロジェクトルートに作成します。

```python
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
```

ここでは、データベースとのセッションを管理するためのセッションファクトリを作成しています。
各リクエストでセッションを発行し、利用後に閉じることで接続管理を行います。

```python
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
```

SQLAlchemyのモデルの継承元クラスとして利用され、後述するモデルクラスはこのBaseを継承して定義されます。

```python
Base = declarative_base()
```

Userモデルのコードは以下の通りです。

```app/models/user.py
from sqlalchemy import Column, Integer, String
from app.database import Base

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)
```

ユーザ情報を格納するテーブルを定義しています。

- id: 主キー
- name: ユニークかつインデックス付きのユーザ名
- email: ユニークかつインデックス付きのメールアドレス

このモデルは CRUD 操作で利用され、ユーザ作成や取得に使われます。

`app/schemas/user.py`では、pydanticを用いたスキーマを定義しています。
クライアントから受け取るデータやレスポンスとして返すデータの構造を定義します。
ここでは Userの各スキーマを定義しています。

```app/schemas/user.py
from pydantic import BaseModel

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    pass

class User(UserBase):
    id: int

    class Config:
        orm_mode = True
```

Configの`orm_mode = True `により、SQLAlchemy のオブジェクトをそのままpydanticオブジェクトとして変換できるようにしています。

次に`app/crud/user.py`の説明です。
これは、CRUD操作を担当する関数群です。

```app/crud/user.py
from sqlalchemy.orm import Session
from app.models.user import User
from app.schemas.user import UserCreate

def get_user(db: Session, user_id: int):
    return db.query(User).filter(User.id == user_id).first()

def get_user_by_email(db: Session, email: str):
    return db.query(User).filter(User.email == email).first()

def get_users(db: Session, skip: int = 0, limit: int = 100):
    return db.query(User).offset(skip).limit(limit).all()

def create_user(db: Session, user: UserCreate):
    db_user = User(name=user.name, email=user.email)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user
```

各関数は以下のような処理です。

- get_user: 指定した user_id のユーザをデータベースから取得
- get_user_by_email: メールアドレスからユーザを検索し、重複登録を防ぐために使用
- get_users: ページネーション対応で複数のユーザを取得
- create_user: 渡されたユーザデータ（UserCreate スキーマ）から新たな User モデルのインスタンスを作成し、DB に追加・コミット、そして登録後のインスタンスを返却する

次に`app/routers/user.py`についてです。

FastAPI のエンドポイント（ルート）を定義するファイルです。
リソースに対する API操作を実装しています。

```app/routers/user.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import SessionLocal
from app.schemas.user import User, UserCreate
from app.crud import user as crud_user

router = APIRouter(
    prefix="/users",
    tags=["users"]
)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    db_user = crud_user.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return crud_user.create_user(db=db, user=user)

@router.get("/{user_id}", response_model=User)
def read_user(user_id: int, db: Session = Depends(get_db)):
    db_user = crud_user.get_user(db, user_id=user_id)
    if db_user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return db_user

@router.get("/", response_model=list[User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    users = crud_user.get_users(db, skip=skip, limit=limit)
    return users
```

```python
router = APIRouter(
    prefix="/users",
    tags=["users"]
)
```

`/users`で始まるエンドポイントをまとめ、SwaggerUI上で「users」タグとして表示されます。


```python
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
```

`get_db`関数は、各エンドポイントで DBセッションを発行し、リクエスト終了時に自動でクローズする仕組みを提供しています。

`app/routers/user.py`で作成しているエンドポイントは以下の通りです。

* POST /users/:

リクエストボディの内容（UserCreateスキーマ）を受け取り、既に登録済みのメールがないかチェックした後、`crud_user.create_user`を呼び出してユーザを作成します。

* GET /users/{user_id}:

URL のパスパラメータからユーザIDを取得し、DB からユーザを検索、見つからなければ404エラーを返します。

* GET /users/:

ページネーション用のパラメータ（skip, limit）を受け取り、すべてのユーザを取得して返します。

## APIサーバ起動

ここまでファイルを作成したら、プロジェクトディレクトリ直下で以下のコマンドを実行しましょう。

```bash
uvicorn app.main:app --reload
```

すると、APIサーバが立ち上がり、APIにアクセスできるはずです。

<http://127.0.0.1:8000/docs> にアクセスすると、下記のようなSwaggerUIの画面が立ち上がり、APIエンドポイント一覧が確認出来るはずです。
画面上からAPIにリクエストすることも出来ます。

![API.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3996812/35a9c798-c25b-62d7-4a4c-ead2dabe91f8.png)

コマンドラインからcurlでリクエストも出来ます。

```bash
curl -X 'GET' \
  'http://127.0.0.1:8000/users/?skip=0&limit=100' \
  -H 'accept: application/json'
```

```bash
[
  {
    "name": "test",
    "email": "test@example.com",
    "id": 1
  }
]
```

## まとめ

本記事では、FastAPIとSQLAlchemyを使って、シンプルなユーザ情報APIを作成する手順を解説しました。

### 学んだポイント

* FastAPIの基本: 軽量で高速なPython製Webフレームワークであり、型安全性や自動APIドキュメント生成が特徴

* SQLAlchemyの基本: ORMを利用することで、SQLを直接書かずにデータベース操作が可能
* venv を利用した仮想環境のセットアップと、必要パッケージのインストール
* APIの実装: FastAPI を用いたルーティング、データベースとの接続、CRUD操作の実装
* APIのテスト: uvicornでローカルサーバを起動し、SwaggerUIやcurlでAPIの動作確認

今回のAPIはシンプルなCRUD操作のみですが、以下のような発展的な機能を追加することで、より実用的なAPIを作成できます。

* 認証・認可の実装: JWTトークンを利用したユーザ認証
* データバリデーションの強化: Pydanticの機能を活用
* テストの追加: pytest を使用したユニットテスト
* データベースの移行管理: Alembic を利用したマイグレーション管理

FastAPIとSQLAlchemyを活用することで、開発効率の高いAPIを短時間で構築できます。ぜひ、実際のプロジェクトでも活用してみてください!!

----

## KIYOラーニング株式会社について
当社のビジョンは『世界一「学びやすく、分かりやすく、続けやすい」学習手段を提供する』ことです。革新的な教育サービスを作り成長させていく事で、オンライン教育分野でナンバーワンの存在となり、世界に展開していくことを目指しています。

https://kiyo-learning.com/

### プロダクト


* [スタディング](https://studying.jp/)：「学びやすく・わかりやすく・続けやすい」オンライン資格対策講座
* [スタディングキャリア](https://career.studying.jp/)：資格取得者の仕事探しやキャリア形成を支援する転職サービス
* [AirCourse](https://aircourse.com/)：受け放題の動画研修がついたeラーニングシステム（LMS）

### KIYOラーニング株式会社では一緒に働く仲間を募集しています

https://www.wantedly.com/companies/kiyo-learning/projects

https://herp.careers/v1/kiyolearning