FAST APIを使って簡易CRMを作った話し その1

# はじめに

この記事は JSL (日本システム技研) Advent Calendar 2024 - Qiita 1日目の記事です(カレンダー的には2日目です:sweat_smile:)。

今回は、業務で聞くことが多くなってきたもの、まだ触れていなかったFAST APIを使って個人的なCRMを作ってみたいと思い記事にしました。

長編になりそうなので、メモ代わりに何回かに分けて記事にします。
今回は、FAST APIの機能を試してみつつ、簡単なデータ定義とAPIモックの作成までをまとめます。

# FAST APIとは？
FAST APIには以下のような特徴があります。

1. OpenAPI準拠のAPIが作れる
1. 自動ドキュメント生成機能でSwagerが作れる
1. [Pydantic](https://docs.pydantic.dev/latest/)を使ったデータ定義が出来る
1. [Starlette](https://www.starlette.io/)を使ったASGI準拠のアプリが作れる

https://fastapi.tiangolo.com/ja/


私としては、普段`Django REST framework`（以下DRF）を使用してAPI開発をすることが
多いですが、それと比較してどの程度生産性が高いかも興味があります。

# 使ってみる
環境：
・macOS 12.6 Apple M1 Pro
・Python 3.12.7 


今回が全てのオプション込みでインストールする以下を指定しました。
`$ pip install "fastapi[all]"`

※本番環境等で使用する場合は、以下のようにインストールするのが望ましいようです。
`$ pip install "fastapi"`
`$ pip install "uvicorn[standard]"`

任意のフォルダ配下に`main.py`を作成し、以下のコードを記述します。
```main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}
```

その後、
`$ uvicorn main:app --reload`を実行し、ブラウザで
`http://127.0.0.1:8000`を実行すればルートをエンドポイントとしたGetメソッドのAPIが立ち上がります。

`http://127.0.0.1:8000/docs/`にアクセスすると自動生成された
Swaggerのドキュメントが参照できます。

![Screen Shot 2024-11-30 at 16.01.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20512/55a944a7-d3f6-fd42-20e3-1b31832317a9.png)

# CRUD APIを作ってみる

簡易的な顧客管理のCRUDを作ってみます。
データの永続化には、`sqlite`は利用してフォルダ構成は
今後の拡張性と保守性を考慮して、公式が出している

[full-stack-fastapi-template](URL "https://github.com/fastapi/full-stack-fastapi-template")を参考にして以下の様にしました。

```
backend
├── app
│   ├── api
│   │   └── routes
│   │       ├── __init__.py
│   │       └── main.py
│   ├── core
│   │   ├── __init__.py
│   │   ├── config.py
│   │   └── db.py
│   ├── main.py
│   ├── models.py
│   └── tests
└── requirements.txt
```

## Hello world書き換えてみる

`APIRouter`を使ってルーティング処理を分けます。

```app/main.py
from fastapi import FastAPI
from app.api.main import api_router

app = FastAPI()

app.include_router(api_router)
```

```app/api/main.py
from fastapi import APIRouter

from app.api.routes import hello

api_router = APIRouter()
api_router.include_router(hello.router, tags=["hello"])
```

```app/api/routes/hello.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/")
def root():
    return {"message": "Hello World"}
```

`backend`フォルダ配下で
`$ uvicorn app.main:app --reload`
を実行します。

## CRUDのモックを作成する

顧客の情報は以下とします。
```app/models.py
class Customer(BaseModel):
    id: int
    name: str
    address: str
```

```app/api/main.py
from fastapi import APIRouter

from app.api.routes import hello,customer

api_router = APIRouter()
["customer"])
```

```app/api/routes/customer.py
from typing import List
from fastapi import APIRouter

from app.models import Customer

router = APIRouter()

customers = [
    {"id":1, "name": "A社", "address": "長野県長野市xxx町"},
    {"id":2, "name": "B社", "address": "長野県松本市yyy町"},
]


@router.get("/customers", response_model=List[Customer])
async def read_customers():
    return customers


@router.post("/customers", response_model=Customer)
async def create_customer(customer: Customer) :
    return customer


@router.put("/customers/{customer_id}", response_model=Customer)
async def update_customer(customer_id: int, customer: Customer):
    return customer


@router.delete("/customers/{customer_id}")
async def delete_customer(customer_id: int):
    return {"message": "Customer deleted"}
```

`http://127.0.0.1:8000/docs/`でCRUD APIが作成されていることが確認できます。

![Screen Shot 2024-11-30 at 21.22.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20512/622dd21d-decb-407c-61c4-a812c6fdb51a.png)

現状のフォルダ構成は以下となります。
```
backend
.
├── app
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── routes
│   │       ├── __init__.py
│   │       └── customer.py
│   ├── core
│   │   ├── __init__.py
│   │   ├── config.py
│   │   └── db.py
│   ├── main.py
│   ├── models.py
│   └── tests
└── requirements.txt
```

# まとめ
今回、FAST APIを触ってみましたが、簡単にAPIを実装できた印象でした。
現時点では、データ永続化や、バリデーションの実装は出来ていないためDRFとの
生産性の比較は難しいです。

開発を行う場合は、公式にあったようなフォルダ構成を採用しないと厳しい印象です。
また、`models.py`へのデータ定義の仕方については検討の余地があると思っています。

次回は、sqlliteによるデータ永続化を中心にまとめようと思います。