🇯🇵

FastAPI×MongoDBを用いたAPI開発の手ほどき① 〜環境構築〜

2024/02/20に公開

はじめに

こんにちは。LIRIS株式会社で取締役兼エンジニアをしています田村です。
4年ほどFastAPIを使ったAPI開発をしています。
昔はFlaskを扱っていましたが、FastAPIは軽量でAPIドキュメントを自動生成してくれるらしいということで乗り換えました。記法もFlaskに似ているため、Flask経験者はとっつきやすいと思います。

また、DBはPostgreSQLを使っていましたが、複雑な構造のデータ処理をすることが増え、パフォーマンス重視になってきたことからNoSQLのMongoDBを使うことにしました。
FastAPI×MongoDBで開発するようになり、パフォーマンスが向上、SQLAlchemyなどを利用する必要がなくなったため非常に開発しやすくなりました。

ここではFastAPIを扱う中で、最初に知っておいた方が良かったなと思うところや、初心者の方が躓きやすい部分の解決方法をまとめていこうと思います。
環境構築からWeb APIの開発まで、誰かの「手ほどき」になれば幸いです。

本稿では、今後の開発のための環境構築を一から解説していきます。
(FastAPIの公式からDockerイメージが配布されていますが、自分好みの環境を作ったりすることも大事だと思いますので、Dockerファイルから作っていきます。)

コードだけ見たいという方はこちらから取得してください。

想定読者

  • PythonでWeb API開発したい、されている方
  • FastAPIに興味のある方、数年使っている方
  • パフォーマンス向上に興味のある方

FastAPIの環境構築

Dockerのインストール

すでにDockerがPCにインストールされている場合、この工程は不要です。
お手持ちのPCのOSに合ったDockerをインストールしてください。

プロジェクト用のフォルダ作成、ファイル作成

今回は「fastapi-mongo-example」というフォルダを作成しました。
作成したフォルダ内に以下のようにフォルダとファイルを作成します。ファイルの中身はこの後記述していきます。

fastapi-mongo-example/
├ app/
│ ├ .dockerignore
│ └ Dockerfile
├ .gitignore
└ docker-compose.yml

.gitignoreの記述

.gitignoreファイルに以下の記述をします。
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# mac
.DS_Store

# 開発用DB
mongo_dev/

.dockerignoreの記述

.dockerignoreファイルに以下の記述をします。
# git
.git
.gitignore

# Docker
Dockerfile
.dockerignore

# Python
__pycache__

# others
README.md
.DS_Store

Dockerfileの記述

先程作成したDockerfileに以下の記述をします。後ほど追記があります。
このDockerfileは、FastAPIが動作する環境を構築するための内容が記載されます。

FROM python:3.11.1-slim-bullseye

WORKDIR /app

COPY ./ ./

RUN apt-get update \
  && apt-get upgrade -y \
  && pip install -U pip \
  && pip install poetry \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

ENV PYTHONPATH=/app
ENV LANG ja_JP.UTF-8

docker-compose.ymlの記述

docker-compose.ymlには以下の記述をします。後ほど追記があります。
docker-compose.ymlは、複数のDockerコンテナを管理するための設定ファイルになります。今回は以下3つのコンテナを管理します。

  • fastapi: FastAPI
  • mongo: MongoDB
  • mongoexpress: MongoDBのGUIツール
docker-compose.yml
version: "3.8"

services:
  fastapi:
    image: "fastapi:${TAG-latest}"
    links:
      - mongo
    build:
      context: ./app
      dockerfile: Dockerfile
    ports:
      - 8000:8000
    volumes:
      - ./app:/app:cached
    tty: true
    restart: unless-stopped

  mongo:
    image: mongo:5.0
    ports:
      - 27017:27017
    environment:
      MONGO_INITDB_DATABASE: app
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - ./mongo_dev:/data/db
    tty: true
    restart: unless-stopped

  mongoexpress:
    image: mongo-express:0.54
    links:
      - mongo
    ports:
      - 8081:8081
    environment:
      VCAP_APP_HOST: 0.0.0.0
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: password
    restart: unless-stopped

※ 本番環境のパスワードなどの機密情報は、絶対ベタ書きしないようにしましょう。今回はローカルだけの想定ですので雑な上ベタ書きです。

Dockerイメージのビルド

ターミナルを開き、作成したfastapi-mongo-exampleフォルダ内で docker-compose build を実行。

Dockerコンテナの起動

Dockerイメージができあがったので、Dockerコンテナを起動します。ターミナルで docker-compose up -d を実行します。以下のように表示されればOKです。

$ docker-compose up -d
[+] Building 0.0s (0/0)
[+] Running 4/4
 ✔ Network fastapi-mongo-example_default           Created
 ✔ Container fastapi-mongo-example-mongo-1         Started
 ✔ Container fastapi-mongo-example-fastapi-1       Started
 ✔ Container fastapi-mongo-example-mongoexpress-1  Started

コンテナ内にアクセス

fastapiコンテナにアクセスします。ターミナルで docker-compose exec fastapi /bin/bash を実行します。
以下のように表示されればOKです。

$ docker-compose exec fastapi /bin/bash
root@851397f2ad9e:/app#

必要ライブラリのインストール

今回はPoetryを用いてPythonライブラリをインストールします。
pipが好みという方はpipで大丈夫です。

Poetryの初期設定

  1. poetry init
  2. Package name[app]: → Enter
  3. Version[0.1.0]: → Enter
  4. Description[]: → Enter
  5. Author[None, n to skip]: → n → Enter
  6. License[]: → Enter
  7. Compatible Python versions[^3.11]: → Enter
  8. Would you like to define your main dependencies interactively? (yes/no)[yes]→ no → Enter
  9. Would you like to define your development dependencies interactively? (yes/no)[yes]→ no → Enter
  10. Do you confirm generation? (yes/no)[yes]→ Enter

上記を実行するとappフォルダ内に"pyproject.toml"ファイルが作成されます。

Poetryを用いて必要なライブラリをインストール

ここでの注意点としては pydantic[email] のように[]が付いているものはダブルクォーテーションで括ってください。

poetry add fastapi uvicorn pymongo pydantic-settings "pydantic[email]" httpx python-multipart

開発の時だけ使うライブラリは以下のようにインストールします。

poetry add --group dev isort black flake8

これでFastAPIを使えるようになります。

FastAPI × uvicornの起動

main.pyの作成

FastAPIを動かすためにメインのコードを書いていきます。
appフォルダ内に main.py ファイルを作成し、以下のように記述します。

from fastapi import FastAPI

app = FastAPI(title="FastAPI Sample")

@app.get("/", status_code=200)
def root():
    return "成功!"

ファイルを保存できたら、ターミナルで root@01234 のように表示されていることを確認し、以下のコマンドを入力します。

poetry run uvicorn --host 0.0.0.0 --port 8000 main:app

# 以下のような表示になればOK
INFO:     Started server process [58]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

最後の main:app の部分は、main が実行ファイル名、app はmain.pyに記述した app = FastAPI(title="FastAPI Sample") 部分のappを指しています。名称を変えたい場合はこのあたり気をつけてください。

起動確認

ここまでうまくできていればFastAPIのサーバが起動できているはずです。

起動確認のため、Chromeなどのブラウザを開き、http://localhost:8000/ にアクセスしてみましょう。画面に"成功!"と表示されていれば問題ありません。
また、デフォルトでSwaggerUIによるAPIドキュメントが自動生成されていますので、http://localhost:8000/docs にアクセスしてみるとAPIドキュメントが見られます。Swaggerですので実際にAPIをテストできます。

docs

起動確認ができたらCtrl+Cでuvicornを停止します。また、ターミナルに root@000000 のように表示されていれば、exit と入力しEnterキーでDockerコンテナから抜け出しましょう。

シェルスクリプトの作成

毎回poetry runうんたらかんたらと書くのは面倒なので起動用のシェルスクリプトを作成します。
appフォルダ内に start.sh ファイルを作成し、以下記述します。

start.sh
#! /usr/bin/env sh
set -e

HOST=${HOST:-0.0.0.0}
PORT=${PORT:-8000}
LOG_LEVEL=${LOG_LEVEL:-info}
LOGCONFIG=${LOGCONFIG:-"./logging.conf"}
WORKERS=${WORKERS:-1}
APP_MODULE=${APP_MODULE:-"main:app"}

exec poetry run uvicorn \
  --reload \
  --host $HOST \
  --port $PORT \
  --log-level $LOG_LEVEL \
  --log-config $LOGCONFIG \
  --workers $WORKERS \
  "$APP_MODULE"

logging.confの作成

Docker Desktopやターミナルで docker-compose logs fastapi のコマンドを実行すると、ログが表示されます。
しかし、uvicornのデフォルトログ形式だとエンドポイントの実行時間が含まれていなかったりしますので、ログのフォーマットを変更します。

appフォルダ内に logging.conf ファイルを作成し、以下記述します。
(お好みでカスタマイズしていただいて大丈夫です。)

logging.conf
[loggers]
keys=root

[handlers]
keys=stderrHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=stderrHandler

[handler_stderrHandler]
class=StreamHandler
formatter=simpleFormatter
args=(sys.stderr,)
propagate=False

[formatter_simpleFormatter]
format=[%(asctime)s][%(levelname)s][%(process)d](%(filename)s:%(lineno)s) %(message)s

Dockerfileの修正

Dockerfileを以下のように修正します。
RUNの中にpoetry installコマンドを追記したのと、最後に1行追記しています。

Dockerfile
FROM python:3.11.1-slim-bullseye

WORKDIR /app

COPY ./ ./

RUN apt-get update \
  && apt-get upgrade -y \
  && apt-get install wget -y \
  && pip install -U pip \
  && pip install poetry \
  && poetry run pip install -U pip setuptools \
  && poetry install \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

ENV PYTHONPATH=/app
ENV LANG ja_JP.UTF-8

CMD [ "./start.sh" ]

docker-compose.ymlの修正

環境変数を追加します。
(mongoとmongoexpressは変更なしです。)

docker-compose.yml
version: "3.8"

services:
  fastapi:
    image: "fastapi:${TAG-latest}"
    links:
      - mongo
    build:
      context: ./app
      dockerfile: Dockerfile
    ports:
      - 8000:8000
    volumes:
      - ./app:/app:cached
    environment:
      HOST: 0.0.0.0
      PORT: 8000
      LOG_LEVEL: info
      LOGCONFIG: ./logging.conf
      WORKERS: 1
      APP_MODULE: main:app
    tty: true
    restart: unless-stopped

Dockerイメージの再構築

Dockerfile、docker-compose.ymlを修正したら、ターミナルで docker-compose build を実行し、Dockerイメージを再構築します。
再構築できたか確認するために、docker-compose up -d でDockerコンテナを起動します。
先程と同様に、ブラウザで http://localhost:8000/docs にアクセスし、APIドキュメントが見られればOKです。

まとめ

長くなってしまいましたので、今回はここまでとします。

今回実施したこと

  1. Dockerを用いたFastAPI環境構築
  2. docker-compose.ymlの設定
  3. FastAPIの起動確認

ここまでのコードはこちらから取得できます。

次回はFastAPIとMongoDBを接続し、サンプルコードを書きながら動作を見ていこうと思います。

この記事を読んでみて何か学びが得られれば幸いです。

Discussion