４０代おっさんFastAPI、MongoDB連携メモ①

## 本記事について

この記事はプログラミング初学者の私が学んでいく中でわからない単語や概要を分かりやすくまとめたものです。
もし不正などありましたらコメントにてお知らせいただければ幸いです。

## 環境ファイルを作る

プロジェクトの直下に`.env`を作る
次にMongoDBからConnectのパスをコピー
`<password>`部分を自分で作ったものに変える

```
MONGO_API_KEY=mongodb+srv://XXXXXXXXXXXXX
```

次にプロジェクトの直下に`database.py`を作る
もう1つ`schemas.py`を作る

### database.py

こちらにはMongoDBと連携する処理を書く

```python
from decouple import config
from typing import Union # 選択肢が２つ以上の場合に使える
import motor.motor_asyncio # こちらでMongoDB連携のために使う

MONGO_API_KEY = config('MONGO_API_KEY') # configを使って環境変数読み込む

client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_API_KEY) # MongoDB連携 APIのキーを渡す
database = client.API_DB # MongoDBの名前と一致
collection_todo = database.todo # MongoDBの名前と一致
collection_user = database.user # MongoDBの名前と一致

## insert_oneを辞書型で返すために必要な関数
def todo_serializer(todo) -> dict:
    return {
        "id": str(todo["_id"]),
        "title": todo["title"],
        "description": todo["description"]
    }

async def db_create_todo(data: dict) -> Union[dict, bool]: # Unionで選択肢をdict, bool選べるようにしている
    todo = await collection_todo.insert_one(data) # insert_oneの返り値 insertResult(インスタンス)返り値になる
    new_todo = await collection_todo.find_one({"_id": todo.inserted_id}) # find_oneでidを取得
    ## find_oneのあるなし判定
    if new_todo:
        return todo_serializer(new_todo) 
    return False
```

- `insert_one`の返り値 insertResult(インスタンス)返り値になる。`insert_id`と言う属性が付与され、追加したIDを取得される。
- insert_oneで返されたinsertResult(インスタンス)はオブジェクトクラスから生成されたインスタンスなので単純なstring型にならない(タイムスタンプなど色々入っている)
- `find_one`でidを取得 `_id`で格納されている。
- `find_one`のリターンのタイプは
  - ある場合はドキュメントを返して
  - 無い場合はnoneを返す

### schemas.py

エンドポイントに渡すデータ型
レスポンスのデータ型を定義する。

```python
from pydantic import BaseModel # 型を定義するために使う

# エンドポイント返すデータ型
class Todo(BaseModel):
    id: str
    title: str
    description: str

# 新規作成に渡すデータ型
class TodoBody(BaseModel):
    title: str
    description: str

# mainのrootのデータ型
class SuccessMsg(BaseModel):
    message: str
```

### route_todo.py

エンドポイントを作る

```python
from fastapi import APIRouter # routerを作るのに必要
from fastapi import Response, Request, HTTPException # 11行目、21行目の関数
from fastapi.encoders import jsonable_encoder # jsonable_encoder型で受け取ったデータを辞書型にするために必要
from schemas import Todo, TodoBody # 型定義
from database import db_create_todo # database.pyで作った関数
from starlette.status import HTTP_201_CREATED # ステータスコードを上書きするのに必要

router = APIRouter() # インスタンス化

@router.post("/api/todo", response_model=Todo)
async def create_todo(request: Request, response: Response, data:TodoBody): # 実行されたときの関数を定義
    todo = jsonable_encoder(data) # 受け取ったdataを辞書型にする
    res = await db_create_todo(todo)
    response.status_code = HTTP_201_CREATED # ステータスコードの上書き
    # jsonable_encoder型存在していたらデータを存在しなければnoneを返すので判定する
    if res:
        return res
    # db_create_todoでFalseの場合の例外処理
    raise HTTPException(
        status_code=404, detail="Create task failed"
    )
```

### main.py

```python
from fastapi import FastAPI
from routers import route_todo # 作ったroutersのroute_todo
from schemas import SuccessMsg

app = FastAPI()
app.include_router(route_todo.router) # appにルートを追加

@app.get("/", response_model=SuccessMsg)
def root():
    return {"message": "利樹大好きだよ～～ん"}
```

### 参考

https://www.udemy.com/course/farm-stack-react-fastapi