FastApi で CRUD を試して見た時のメモ

前回の記事で API と Render について触れました。
今回はデータベースにつないで CRUD を試してみたいと思います。
環境は Docker を使ってアプリケーションとデータベースのコンテナを分けています。

参考

階層

階層は下の通りです。

project
├── backend
│   ├── db.py # 作成
│   ├── main.py # 編集
│   ├── models.py # 作成
│   ├── schemas.py # 作成
│   ├── static
│   │   └── styles.css
│   ├── templates
│   │   └── main.html
│   └── user.py # 作成
├── database
├── frontend
├── mysql
├── docker-compose.yml
├── dockerfile
└── requirements.txt

インストール

前回に続いてデータベース関連のライブラリをインストールします。
Django では ORM が備わっていますが、FastApi は無いのでインストールが必要のようです。
sqlalchemy は、スターが一番多いよう。

pip install sqlalchemy pymysql

前回の記事も合わせると requirements.txt は下記のようになりました。

anyio==3.6.1
click==8.1.3
fastapi==0.81.0
greenlet==1.1.3
h11==0.13.0
httptools==0.4.0
idna==3.3
Jinja2==3.1.2
MarkupSafe==2.1.1
pydantic==1.10.1
PyMySQL==1.0.2
python-dotenv==0.21.0
PyYAML==6.0
sniffio==1.3.0
SQLAlchemy==1.4.40
starlette==0.19.1
typing_extensions==4.3.0
uvicorn==0.18.3
uvloop==0.16.0
watchfiles==0.16.1
websockets==10.3

docker-compose の設定

docker-compose の一部を切り出して記載しておきます。
ここでデータベースとユーザーの情報を定義しています。

version: '3'
services:
  ...(略)...
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: database_name
      MYSQL_USER: user_name
      MYSQL_PASSWORD: user_password
      MYSQL_ROOT_PASSWORD: root_user_password
    ports:
      - 33306:3306
    ...(略)...

データベース接続

データベース接続には db.py で定義するようです。

from sqlalchemy import create_engine, MetaData
engine = create_engine(
    'mysql+pymysql://user_name:user_password@db:3306/database_name')
meta = MetaData()
conn = engine.connect()

テーブル設計

テーブル設計は models.py で定義するようです。

from sqlalchemy import Table, Column, Integer, String
from db import meta, engine
users = Table('users', meta,
              Column('id', Integer, primary_key=True, autoincrement=True),
              Column('name', String(255)),
              Column('email', String(255)),
              Column('password', String(255)))
meta.create_all(engine)

型の指定

リクエストやレスポンスの型を定義しているようです。

from pydantic import BaseModel
class User(BaseModel):
    name: str
    email: str
    password: str

ルーティング

main.py に app.include_router(user) を定義して、uses.py にルーティング出来るようにします。

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from user import user
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def render_main(request: Request):
    return templates.TemplateResponse("main.html", {"request": request, "message": "Hello RestApi"})
app.include_router(user)

処理

main.py から流れてきて実際に処理をしてレスポンスを返します。
CRUD それぞれの処理が書かれています。

from fastapi import APIRouter
from models import users
from db import conn
from schemas import User
user = APIRouter()
@user.get('/user/')
async def fetch_users():
    return conn.execute(users.select()).fetchall()
@user.get('/user/{id}')
async def fetch_user(id: int):
    return conn.execute(users.select().where(users.c.id == id)).first()
@user.post('/user/')
async def create_user(user: User):
    conn.execute(users.insert().values(
        name=user.name,
        email=user.email,
        password=user.password
    ))
    return conn.execute(users.select()).fetchall()
@user.put('/user/{id}')
async def update_user(id: int, user: User):
    conn.execute(users.update().values(
        name=user.name,
        email=user.email,
        password=user.password
    ).where(users.c.id == id))
    return conn.execute(users.select()).fetchall()
@user.delete('/user/{id}')
async def delete_user(id: int):
    conn.execute(users.delete().where(users.c.id == id))
    return conn.execute(users.select()).fetchall()

確認

  1. http://localhost:8000/docs にアクセスする。
  2. POST /user/ Create User をクリックして開く。
  3. Try it out をクリックする。
  4. JSON が表示されるので値を適当に修正する。
  5. Execute をクリックする。
  6. テーブル users の中身を確認する。
mysql> SELECT * FROM users;
+----+------+-------+----------+
| id | name | email | password |
+----+------+-------+----------+
|  1 | hoge | fuga  | bar      |
+----+------+-------+----------+
1 row in set (0.00 sec)
YouTube