無料でFastAPIで作ったAPIをデプロイする
はじめに
今回、はじめてZennで記事を書いてみることにしました。
記事を書くことに慣れていないため、おかしな点があるかもしれませんがご了承下さい。
初めての記事の題材に、FastAPIをDeta Spaceにデプロイするというテーマを選んだ理由は、実際に自分自身で使用していて、日本語の記事があまり見つからなかったためです。
少しでもFastAPIをデプロイしたいという方のお役に立てるよう、記事を書くことにしました。
実装やデプロイを完了した後に記事を執筆しているため、キャプチャがない部分や省略した箇所がありますが、ご了承ください。
FastAPIとは
個人的に一言でまとめると簡単、早い、軽いです。
コードを書くと自動的にAPIドキュメントが生成される点が特に優れていると感じています。
詳しくはFastAPIの公式サイトに特徴がまとまっているので参照してください。
Deta Spaceとは
以前はDeta Cloudという名称のサービスでしたが、現在はDeta Spaceに名称が変更されています。
以下のクイックスターターガイドが用意されているのでFastAPI(Python)以外も簡単にデプロイできると思います。
- Next
- Nuxt
- Svelte
- Node.js
- Python
- Go
- Rust
Deta Spaceの設定
はじめにDeta Spaceの設定から行います。
アカウント登録
公式サイト右上の「Sign Up」からアカウント登録を行います。
CLIのセットアップ
Deta Spaceでは専用のCLIを使ってデプロイを行います。
下記からCLIのインストールとアクセストークンを用いた認証を行います。
Collectionの作成
RDBでいうCollectionがデータベース、Baseがテーブルという役割になります。
CollectionsのNew Coollection
から新規のCollectionを作成します。
今回はusers
という名前で作成します。
作成したCollectionのData Keyを作成
作成したCollectionを選び、右上のCollection Settings
→ Create new data key
でData keyを作成できます。
作成したData keyはDBアクセスに使うので保存しておきます。
今回デプロイするFastAPIについて
この記事ではPythonのバージョンは3.9を使用しています。
ローカル環境では、Pyenvを使用して仮想環境を構築し、Uvicornを利用して実行します。
本記事では仮想環境でのセットアップや動かし方については説明しませんが、必要に応じて別途調べて設定してください。
今回サンプルとして作成したAPIはusers
というテーブルへの基本的なCRUD機能です。
エンドポイントは以下の5つです。
No | HTTP | URI | 名前 |
---|---|---|---|
1 | GET | /users | 全件取得 |
2 | POST | /users | 新規作成 |
3 | GET | /users/{key} | 1件取得 |
4 | DELETE | /users/{key} | 削除 |
5 | PATCH | /users/{key} | 更新 |
フォルダ構成
APIの実装はmain.py
ファイルにまとめることもできますが、今回はより拡張性を持たせるために、apis
ディレクトリにファイルを分割しています。
root/
├ .space/
├ .venv/
├ apis/
│ ├ __init__.py
│ └ users.py
├ .spaceignore
├ database.py
├ main.py
├ requirements.txt
└ Spacefile
各ファイルの説明と今回実装したソースコード
.space/
初回のspace new
実施後に自動的に作成されます。
.venv/
ローカルの仮想環境です。
apis/
今回はusersだけですが、users以外のエンドポイントを用意する場合はファイルを作成します。
apis/users.py
この記事のCRUDのメインとなるファイルです。
型定義やDB処理を別ファイルに切り出すことで、よりスッキリした実装が可能です。
DBへのアクセスはdatabase.py
で定義したget_db_users
をDIを利用してアクセスしています。
他のサービスでDBにMySQLを使う際にDIを使っていたため同じように実装しましたが、必ずしもDIを行わなくても良いと思います。
DetaのSDKの使い方については公式ドキュメントをご参照ください。
from datetime import datetime
from typing import Sequence
from deta import _Base
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from database import get_db_users
class UserReadResponse(BaseModel):
key: str = Field(..., description="キー")
name: str = Field(..., description="名前")
created_at: str = Field(..., description="作成日時")
updated_at: str = Field(..., description="更新日時")
class UserCreateRequest(BaseModel):
name: str = Field(..., description="名前")
class UserCreateResponse(BaseModel):
key: str = Field(..., description="キー")
name: str = Field(..., description="名前")
created_at: str = Field(..., description="作成日時")
updated_at: str = Field(..., description="更新日時")
class UserUpdateRequest(BaseModel):
name: str = Field(..., description="名前")
router = APIRouter()
@router.get("")
def list_users(
db_users: _Base = Depends(db_users),
) -> Sequence[UserReadResponse]:
response = db_users.fetch()
users: Sequence[UserReadResponse] = response.items
return users
@router.post("")
def create_user(
user_body: UserCreateRequest, db_users: _Base = Depends(db_users)
) -> UserCreateResponse:
user_body_dict = user_body.dict()
now = str(datetime.now())
user_body_dict["created_at"] = now
user_body_dict["updated_at"] = now
user: UserCreateResponse = db_users.put(user_body_dict)
return user
@router.get("/{key}")
def get_user(key: str, db_users: _Base = Depends(db_users)) -> UserReadResponse:
user: UserReadResponse = db_users.get(key)
if not user:
raise HTTPException(status.HTTP_404_NOT_FOUND)
return user
@router.delete("/{key}")
def delete_user(key: str, db_users: _Base = Depends(db_users)) -> None:
db_users.delete(key)
@router.patch("/{key}")
def update_user(
key: str, user_body: UserUpdateRequest, db_users: _Base = Depends(db_users)
) -> None:
user_body_dict = user_body.dict()
now = str(datetime.now())
user_body_dict["updated_at"] = now
db_users.update(user_body_dict, key)
database.py
"data_key"
はCollectionsの任意のCollectionを選び、Collection SettingsでData Keyを作成できるので、作成したKeyに変更します。
from deta import Deta, _Base
def get_deta() -> Deta:
deta = Deta("data_key")
return deta
def get_db_users() -> _Base:
deta = get_deta()
db_users = deta.Base("users")
yield db_users
main.py
from fastapi import FastAPI
from apis import users
app = FastAPI()
app.include_router(users.router, prefix="/users", tags=["Users"])
requirements.txt
deta
fastapi
uvicorn
※uvicornはローカルでテストする時のみ使用するためデプロイ時には不要
.spaceignore
このファイルに追加したファイル、ディレクトリはプッシュ時のアップロードから除外することができます。
.gitignore
と似た働きをします。
__pycache__
は追加しなくてもデフォルトで除外されるため、今回はローカルの仮想環境として作成される.venv
を追加しています。
.venv
Spacefile
Deta Spaceで動作させるためのアプリの構成を定義するファイルです。
.space/
と同様に、初回のspace new
実施後に自動的に作成されます。
src
にはmain.pyのパスを書きます。
デプロイするフォルダの直下以外にmain.pyがある場合はパスを修正する必要があります。
# Spacefile Docs: https://go.deta.dev/docs/spacefile/v0
v: 0
micros:
- name: ExampleFastAPI
src: .
engine: "python3.9"
FastAPIをDeta Spaceへデプロイするメリット・デメリット
メリット
Deta Space独自の書き方を覚える必要がありますが、完全無料でDBを用いたREST APIをデプロイできるところが非常に魅力的です。
負荷テストなどはしていないのでどのぐらいの規模まで使用できるのかは分かりませんが、レスポンスも早くちょっとしたAPIを作りたい場合非常に便利だと感じました。
デメリット
まだサービスが提供されたばかりで仕様の変更やユーザー毎の制限が変わることが懸念されます。
Deta Cloud時代に試しでデプロイをしてしばらくたってから、ファイルを修正してデプロイ仕様としたところ、仕様がガラッと変わっていてデプロイできないということが発生しました。
デプロイ先としてDeta Spaceで本当に大丈夫なのか?と不安になってしまう点は一番のデメリットかと思います。
あとは日本語のドキュメントが存在しないという点もデメリットですが、公式ドキュメントが充実しているのでその辺はなんとかなるかなと思います。
Deta Spaceに欲しい機能
-
CDを組みたい
GitHubにプッシュされたら自動でデプロイされたら便利だと感じました。
CLIでアクセスコードを使ってログインをする必要があるので自動デプロイができませんでした。
アクセスコードを引数にしてログイン後デプロイができたら最高です。 -
ブラウザ上での操作性
特にCollectionですが操作性が直感的ではないのでもう少しUI/UXが改善したらなと思います。
あとは私が見つけられていないだけなのかもしれませんが、誤って作成したCollectionやBaseを削除する方法が見つかりません。
もしかしたら削除はできないのかも? -
日本語対応
URLを見ると多言語化対応を見据えた構成になっているので、いずれされるのだろうと予想しています。
やはり日本語化されているだけで手を出すハードルがいっきに下がるので、多言語化対応される時はぜひ日本語も追加してほしいところです。
終わりに
Deta Spaceはフロントエンドのデプロイも可能なので、次はフロントエンドも開発してデプロイして見たいと思います。
VueやReactの無料デプロイはNetlifyやVercelが主流かと思いますが、Deta Spaceでバックエンドもフロントエンドもデプロイできて、さらにDBも賄えるならDeta Spaceという選択肢もありだと思っています。
記事を執筆することにハードルを感じていましたが、自分自身のアウトプットも兼ねてどんどん書いていきたいと思います。
Discussion