Aimless Blog

VercelのServerless FunctionsでFastAPIを使う

Tag:
vercelfastapi

VercelにFastAPIをデプロイして読み込み専用のRestAPIとして利用してみます。 データベースはSQLite3を使用しています。
Serverless Functionsではデータベースに書き込みはできないので書き込みが必要なアプリを作るなら firestoreとか他のサービスと連携してください。

ローカル環境にvenvで仮想環境を作成

python3 -m venv fastapi-vercel

ディレクトリに移動してアクティベート

cd fastapi-vercel
source bin/activate
または
.\scripts\activate

FastAPIをインストール

pip install fastapi

apiディレクトリの作成

Serverless Functionsはapiディレクトリにサポートされている言語ファイル(Node.js: .js .ts, Go: .go, Python: .py, Ruby: .rb )を置くと自動で認識されるので、apiディレクトリを作ってそこにindex.pyを置きます。

Serverless Functions

Within the /api directory of your projects, Vercel will automatically recognize the languages listed on this page, through their file extensions, and serve them as Serverless Function.

プロジェクトの/apiディレクトリ内で、Vercelはこのページに掲載されている言語を、そのファイルの拡張子によって自動的に認識し、Serverless Functionとして提供します。

ディレクトリ構造は以下のようになります

fastapi-vercel
    |--api/
    |   |-- __init.py__
    |   |-- index.py
    |
    |--requirements.txt
    |--vercel.json

index.py

とりあえずテストファイルを作ります。

from fastapi import FastAPI

app = FastAPI()

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

@app.get("/api/test")
async def test():
    return {"message": "Hello Test"}

vercel.json

上記まで作成してVercelにデプロイして"~.vercel.app"や" ~.vercel.app/api"にアクセスしても404が表示されるので、 vercel.jsonでrewritesの設定をします。routesはレガシーな設定なので代わりにcleanUrls, trailingSlash, redirects, rewrites, and/or headersを使いましょう。
参照

vercel.json

{
  "rewrites": [{ "source": "/(.*)", "destination": "/api/index.py" }]
}

requirements.txt

pip freeze > requirements.txtでrequirements.txt作成。

Vercelにデプロイ

GitHubにプッシュしたらVercelでリポジトリを選択してデプロイします。 特に設定することはありません。

エラーがなければCongratulations!と表示されます。

fastapi-vercel1

https://fastapi-vercel-eosin.vercel.app/apiにアクセスすると{"message":"Hello World"}が返っているのが確認できます。

fastapi-vercel2

https://fastapi-vercel-eosin.vercel.app/api/test

fastapi-vercel3

https://fastapi-vercel-eosin.vercel.app/docsでAPIドキュメントも表示された。

fastapi-vercel4

SQL

SQLite3のデータを読み込むのでpip install sqlalchemyでSQLAlchemyをインストール。
あと、async-generatorasync-exit-stackpip install async-generator async-exit-stackでインストール。
import等をする必要はないのですが、この2つがないとVercel上でDBの読み込みができません。
再びpip freeze > requirements.txt

データベース関連ファイルは当ブログFlaskアプリをFastAPIに置き換えるを流用。 apiディレクトリに"databese.py"、"model.py"、"crud.py"、schemas.pyをコピーしていくつか修正。 dbファイルはfastapi-vercel/dbに置きます。

fastapi-vercel
    |--api/
    |   |-- __init.py__
    |   |-- index.py
    |   |-- database.py
    |   |-- model.py
    |   |-- crud.py
    |   |-- schemas.py
    |
    |--db/
    |   |--book.db
    |--requirements.txt
    |--vercel.json

database.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./db/book.db"
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

model.py

from sqlalchemy import Column, Integer, String
from .database import Base


class Book(Base):
    __tablename__ = 'book'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String)
    author = Column(String)
    publisher = Column(String)

crud.py

from sqlalchemy.orm import Session
from . import model


def all_list(db: Session):
    return db.query(model.Book).all()

schemas.py

from pydantic import BaseModel
from typing import Optional

class Datasout(BaseModel):
    title: str
    author: str
    publisher: Optional[str] = None
    id: int

    class Config:
        orm_mode = True

index.py

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from . import crud
from .database import SessionLocal, engine
from .schemas import Datasout
from typing import List

app = FastAPI()

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


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

@app.get("/api/test")
async def test():
    return {"message": "Hello Test"}


@app.get("/api/list", response_model=List[Datasout])
async def read_list(db: Session = Depends(get_db)):
        result = crud.all_list(db)
        return result

Vercelにデプロイ

Vercelにデプロイしてエラーも出なかったのでhttps://fastapi-vercel-eosin.vercel.app/api/listにアクセスしてみると……

fastapi-vercel5

データベースの読み込みに成功してjsonで返ってます。

まとめ

このように読み込み専用のAPIとして利用して、あとはフロントエンドを用意すればVercelで無料で運用できるのですごく便利です。

GitHubリポジトリ